Emacs Lisp Static Analyzer
Switch branches/tags
Nothing to show
Clone or download
Latest commit 9a2f3d5 Nov 20, 2018
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
bin Fix bin/elsa to execute Emacs in a clean environment Nov 10, 2018
dev Add more types Nov 4, 2018
images Add examples of features to README.md Oct 21, 2018
tests Fix tests which now accept Const types Nov 4, 2018
.gitignore Ignore .elc Sep 6, 2018
.travis.yml Add cask link to Elsa in travis setup Aug 5, 2018
Cask Add f dependency Sep 6, 2018
Elsafile.el Move symbol naming to style ruleset and remove symbol ruleset Aug 26, 2018
LICENSE Create LICENSE Aug 6, 2018
README.md Add examples of features to README.md Oct 21, 2018
elsa-analyser.el Add constant types (#44) Nov 3, 2018
elsa-check.el Change defmethod to defgeneric when defining interfaces Aug 17, 2018
elsa-english.el [#54] Change the comment type annotation syntax to include the functi… Aug 13, 2018
elsa-error.el [Fix #59] Provide nicer API for creating messages Sep 10, 2018
elsa-extension-builtin.el Do not treat t and nil as constant symbols Nov 4, 2018
elsa-extension-cl.el Rename elsa-form-name to elsa-get-name Sep 8, 2018
elsa-extension-dash.el Add partial extensions for -let and -let* Sep 10, 2018
elsa-extension-eieio.el Add eieio types Aug 10, 2018
elsa-extension-elsa.el Use the actual form name in hint instead of placeholder 'x' Sep 10, 2018
elsa-font-lock.el Fix font-locking of final paren of elsa-make-type form Aug 13, 2018
elsa-pkg.el Add pkg file [Fix #124] Nov 19, 2018
elsa-reader.el Add constant types (#44) Nov 3, 2018
elsa-rules-list.el Run the "useless condition" also for when/unless Oct 21, 2018
elsa-ruleset.el Add check for docstrings Sep 9, 2018
elsa-scope.el Add implementation of elsa-scope-narrow-var taking form Oct 25, 2018
elsa-state.el [Fix #15] Add elsa-disable-line Oct 21, 2018
elsa-type-helpers.el Add Const type constructor Nov 3, 2018
elsa-typed-builtin.el [Fix #115] Arithmetic operators accept markers Nov 4, 2018
elsa-typed-cl.el Add typed for cl Aug 11, 2018
elsa-typed-eieio.el Add eieio types Aug 10, 2018
elsa-typed-subr.el Add type annotation for last Sep 8, 2018
elsa-typed-syntax.el Rename typed-emacs-syntax to typed-syntax Sep 6, 2018
elsa-typed-thingatpt.el Add types for thingatpt Aug 11, 2018
elsa-types.el Print const types with %S to have them readable Nov 3, 2018
elsa-variable.el Improve docstrings Oct 25, 2018
elsa.el [Fix #15] Add elsa-disable-line Oct 21, 2018

README.md

Elsa - Emacs Lisp Static Analyser Build Status

(Your favourite princess now in Emacs!)

Coverage Status Paypal logo Patreon

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

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. You can find the examples in examples.el.

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

How do I run it

Currently we only support running Elsa with Cask.

  1. git clone https://github.com/emacs-elsa/Elsa.git somewhere to your computer.
  2. Add (depends-on "elsa") to Cask file of your project
  3. Run cask link elsa <path-to-elsa-repo>
  4. 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-case for naming instead of snake_case.
  • Syntactic, such as checking we are not wrapping the else branch of if with a useless progn.
  • Semantic, such as checking that the condition of if does not always evaluate to non-nil (in which case the if 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).

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 p or -p suffix and PascalCase to get the type:
    • stringpString
    • integerpInteger (Int is also accepted)
    • markerpMarker
    • hash-table-pHashTable
  • 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, so String | Integer is a type accepting both strings or integers.
  • Cons types are specified by prefixing wrapping the car and cdr types with a Cons constructor, so Cons Int Int is a type where the car is an int and cdr is 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 constructor List is also supported.
  • Function types are created by separating argument types and the return type with -> token.
  • To make variadic types (for the &rest keyword) add three dots ... after the type, so String... -> String is 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 Variadic type constructor is also available to construct complex types.
  • To mark type as nullable you can attach ? to the end of it, so that Int? accepts any integer and also a nil. A Maybe type 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.

How to write an extension for your-favourite-package

How to write a ruleset