🎍 Mulang

A universal, multi-language, multi-paradigm code analyzer

Mulang is a tool for analysing source code, which is built on top of five main components:

  1. an Abstract Semantic Tree, an intermediate language which allows to express the semantic - as opposed to syntatic - structure of a multi-paradigm program;
  2. a set of more than 90 inspections for querying code querying code either explicitly - expectations - or implicitlt - smells.
  3. an Expectations Definition Language (EDL), a language for defining custom expectations
  4. a command line tool for analysing both source code in many languages and Mulang's AST. This tool is distributed as both a linux-amd64 binary and a JavaScript package. See downloads section.
  5. higher level interfaces in ruby and javascript that are easier to use and provides some additional capabilities like expectations parsing and automatic internationalized humanization.

Supported languages

Mulang can work with many different programming languages. it natively supports:

In addition, through external tools, it offers support for the following languages:

If you want to use it with a different language, you will have to:

Quick start

The following section uses the ruby interface for demonstration purposes. However, you also can run all the examples either using the comand line tool, or the javascript interface. The javascript version is also available as a ruby gem, suitable for Rails integration

Better than explaining what Mulang is, let's see what it can do for you.

Inspections

Let's suppose we have the following JS code...

let aPlace = buenosAires;
let aBird = {position: aPlace, weight: 20};

...and we want to recognize some code patterns on it. We will first load the expression into Mulang:

> require "mulang"
> code = Mulang::Code.native "JavaScript", "let aPlace = buenosAires; let aBird = {position: aPlace, weight: 20};"

Now we want to know if the code expression uses - that is, contains any reference to - a given identifier. Such identifier could be a variable, function, or anything that has a name:

> code.expect 'Uses:buenosAires'
=> true # because of the reference in `...aPlace = buenosAires...`
> code.expect 'Uses:rosario'
=> false # no reference to the identifier `rosario` is found on the code

Uses:buenosAires is our first inspection: a function that takes a Mulang AST and answers a boolean question about it. That seems easy, but just in case you are wondering: no, Mulang doesn't perform a string.contains or something like that :stuck_out_tongue: :

> code.expect 'Uses:BuenosAires'
=> false # no reference to the identifier `buenos` is found on the code

Contexts

So let's ask something more interesting - does aPlace use the identifier buenosAires?

> code.expect 'aPlace', 'Uses:buenosAires'
=> true # again, because of the the reference in `...aPlace = buenosAires...`
> code.expect 'aPlace', 'Uses:aBird'
=> false # because `...aPlace = buenosAires...` does not reference `aBird`...

Here we have contextualized the inspection, so it runs only within the contexts of the given binding.

Let's tray again: does "aPlace" use rosario? And what about the object aBird? Does it use aPlace or rosario?

> code.expect "aPlace", "Uses:rosario"
=> false
> code.expect "aBird", "Uses:aPlace"
=> true
> code.expect "aBird",  "Uses:buenosAires"
=> true

Oh, wait! Is this a bug? Nope. Expectations are transitive by default: aBird uses aPlace, and aPlace uses buenosAires in turn, which means that aBird actually uses buenosAires. If you don't want that behaviour you can turn it off:

> code.expect "Intransitive:aBird",  "Uses:buenosAires"
=> false

Contexts can be nested, too: for example, if you want to know whether aBird.position uses aPlace - ignoring that weight attribute:

> code.expect "aBird.position", "Uses:aPlace"
=> true
> code.expect "aBird.weight", "Uses:aPlace"
=> false

Inspections do not only allow you to consult usages. They can tell you much more things. See the supported inspections list.

Predicates

Many inspections support an identifier predicate, that is, a matcher for the identifier.

For example, does the former piece of code declare any attribute?

> code.expect 'DeclaresAttribute:*'
=> true
> code.expect 'DeclaresAttribute' # shorter version
=> true

Does it declare an attribute like eight?

> code.expect "DeclaresAttribute:~eight"
=> true

Notice: like in Mulang means that it contains that case-insensitive substring

Finally, does aBird declares an attribute that is not named weight?

> code.expect "DeclaresAttribute:^weight"
=> true # because it also declares position

Matchers

Finally, you can provide a matcher to many of the available expectations, that allows to match specific parts of code with some patterns.

> code.expect 'DeclaresAttribute:WithNumber:20'
=> true  # because weight is initialized with that value
> code.expect 'DeclaresAttribute:WithNumber:21'
=> false
> code.expect 'DeclaresAttribute:position:WithNonliteral'
=> true # because position is initialized with a non-literal value
> code.expect 'DeclaresAttribute:WithLiteral'
=> true # because weight is initialized with a literal value
> code.expect 'DeclaresAttribute:WithNil'
=> false # because no attribute is initialied with null

The complete list of supported matchers is the following:

Contributors