It was announced recently that Ameba is going to be extendable. That means any developer can create his own extension and use together with Ameba’s engine.

Here we will be following through the steps on how to create such an extension and use it.

It is going to be crystal-docs extension, where a new rule will be implemented which will enforce classes to have documentation.

1. New extension skeleton

Creating a new Ameba extension is as simple as creating a new Crystal library:

1

$ crystal init lib crystal-docs && cd crystal-docs



and adding Ameba as a dependency to shard.yml :

shard.yml 1

2

3

4

5

6

7

8

9

10

11

12

13

14

name: ameba-docs

version: 0.1 .0



authors:

- Vitalii Elenhaupt



crystal: 0.29 .0



license: MIT



development_dependencies:

ameba:

github: crystal-ameba/ameba

version: ~> 0.10 .0



It needs to be a development dependency because we don’t want to force the end application to be dependent on a specific version of Ameba.

2. Creating a rule

Ameba enforces rules to be extended from Rule::Base entity and to be a struct. Let’s create one:

src/ameba-docs.cr 1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

module Ameba::Rule

VERSION = "0.1.0"



struct Docs ‹ Base

properties do

description "Enforces public classes to be documented"

end



MSG = "Class must be documented"



def test (source)

AST: :NodeVisitor.new self , source

end



def test (source, node : Crystal: :ClassDef)

return unless node.visibility.public?



doc = node.doc

issue_for(node, MSG) if doc. nil ? || doc.empty?

end

end

end



A couple of things are defined here:

properties defines a dsl for configurable rule properties. We only have a rule description. MSG defines a constant for the error message to be reported. def test(source) is an entry point of the rule. Here the method accepts a source file and passes it to the node visitor, which allows us filter the ClassDef nodes in the method below. def test(source, node : Crystal::ClassDef) is a method which does an actual job. The implementation is pretty self explained: it filters out node, which have public visibility and do not have (or have empty) docs.

The extension is pretty much done. Let’s try to use it.

3. Plug-in the extension

Somewhere in a separate project we want to plug-in the ameba engine and the extension we made.

To do that, we need to add a development dependencies for both of this projects:

shard.yml 1

2

3

4

5

development_dependencies:

ameba:

github: crystal-ameba/ameba

ameba-docs:

github: crystal-ameba/ameba-docs-rule-example



Then we install those deps:

1

2

3

4

5

6

$ shards install



Fetching https://github.com/veelenga/ameba.git

Fetching https://github.com/crystal-ameba/ameba-docs-rule-example.git

Installing ameba (0.10.0 at master)

Installing ameba-docs (0.1.0 at master)



It will create bin/ameba.cr file which is needed to built the ameba together with modules.

First, we need to enable the module:

bin/ameba.cr 1

2

require "ameba/cli"

require "ameba-docs"



And now we are ready to build and run it:

1

2

$ crystal build bin/ameba.cr -o bin/ameba

$ bin/ameba



The results are displayed below. crystal-docs extension in action:

Wrap up

We made a small Ameba extension and used that in a third-party project. Of course, this was just an example of the rule and not a production ready solution. However, it is a perfect example of the power of Ameba’s modules.

The sources are availabe on Github.