Elsa
Elsa copied to clipboard
Emacs Lisp Static Analyzer and gradual type system.
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.
Table of Contents
- State of the project
-
Non-exhaustive list of features
- Detect dead code
- Enforce style rules
- Look for suspicious code
- Track types of expressions
- Understand functional overloads
-
How do I run it
- makem.sh
- Cask
- Flycheck integration
-
Configuration
- Analysis extension
- Rulesets
- Type annotations
- How can I contribute to this project
-
F.A.Q.
- What's up with the logo?
-
For developers
- How to write an extension for your-favourite-package
- How to write a ruleset
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.
Non-exhaustive list of features
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!
Detect dead code
Detect suspicious branching logic
Find unreachable code in short-circuiting forms
Enforce style rules
Provide helpful tips for making code cleaner
Add custom rules for your own project with rulesets
Make formatting consistent
Look for suspicious code
Find references to free/unbound variables
Don't assign to free variables
Detect conditions which are always true or false
Make sure functions are passed enough arguments
Make sure functions are not passed too many arguments
Track types of expressions
Check types of arguments passed to functions for compatibility
Understand functional overloads
downcase
can take a string and return a string or take an int and
return an int. Because we pass a string variable s
, we can
disambiguate which overload of the function must be used and we can
derive the return type of the function as string
instead of (or string int)
.
If we pass an input which doesn't match any overload, Elsa will show a helpful report of what overloads are available and what argument didn't match.
How do I run it
Elsa can be run with makem.sh or with Cask.
makem.sh
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 .sandbox
and avoid
installing Elsa on each run:
- Initialize the sandbox:
./makem.sh -s.sandbox --install-deps --install-linters
. - Run Elsa:
./makem.sh -s.sandbox lint-elsa
.
See makem.sh
's documentation for more information.
Cask
[RECOMMENDED] Using packaged version
This method uses Cask and installs Elsa from MELPA.
- Add
(depends-on "elsa")
toCask
file of your project. - Run
cask install
. -
cask exec elsa FILE-TO-ANALYSE [ANOTHER-FILE...]
to analyse the file.
Using development version
To use the development version of Elsa, you can clone the repository
and use the cask link
feature to use the code from the clone.
-
git clone https://github.com/emacs-elsa/Elsa.git
somewhere to your computer. - Add
(depends-on "elsa")
toCask
file of your project. - Run
cask link elsa <path-to-elsa-repo>
. -
cask exec elsa FILE-TO-ANALYSE [ANOTHER-FILE...]
to analyse the file.
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
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.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-case
for naming instead ofsnake_case
. - Syntactic, such as checking we are not wrapping the else branch of
if
with a uselessprogn
. - Semantic, such as checking that the condition of
if
does not always evaluate tonon-nil
(in which case theif
form 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).
Read the type annotations documentation for more information on how to write your own types.
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.
How to write an extension for your-favourite-package
How to write a ruleset
Acknowledgments
The biggest inspiration has been the [PHPStan][https://github.com/phpstan/phpstan] project, which provided me the initial impetus to start this project. I have went through their sources many times finding inspiration and picking out features.
The second inspiration is [TypeScript][https://www.typescriptlang.org/], which turned a rather uninteresting language into a powerhouse of the (not only) web.
I borrow heavily from both of these projects and extend my gratitude and admiration.