Skip to content

Developer guide

Benoît Garçon edited this page Feb 9, 2020 · 6 revisions

1. Create your project

First step is to create a new maven project.

    mvn -B archetype:generate \
        -DarchetypeGroupId=org.apache.maven.archetypes \
        -DgroupId=fr.cnes.icode \
        -DartifactId=my-custom-plugin

In order to be correctly imported, the groupId of your plugin must be fr.cnes.icode.

Then add official i-Code dependencies from GitHub.

    <repositories>
        <repository>
            <id>jitpack.io</id>
            <url>https://jitpack.io</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>com.github.Facthunder.i-CodeCNES</groupId>
            <artifactId>icode-library</artifactId>
            <version>${icode.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

Value of tag version must be a valid git tag.

2. Declare your own language

You must implement the ILanguage interface and override following methods:

  • getName(): return the display name of the plugin
  • getId(): return your custom plugin identifier
  • getFileExtension(): return the list of file extensions that the plugin can analyze
package fr.cnes.icode.mycustomplugin.language;

import fr.cnes.icode.services.languages.ILanguage;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Definition of the C language.
 */
public class MyCustomLanguage implements ILanguage {

    /**
     * @return the name of the language.
     */
    @Override
    public String getName() {
        return "My Custom Language";
    }

    /**
     * @return the id of the language.
     */
    @Override
    public String getId() {
        return "fr.cnes.icode.mycustomlanguage";
    }

    /**
     * @return the list of extensions of the language.
     */
    @Override
    public List<String> getFileExtension() {
        return Stream.of(
                "bl", "BL", "blue", "BLUE"
        ).collect(Collectors.toList());
    }
}

3. Implement your own checker

3.1. Using plain Java implementation

You can write your own checker in pure Java by overriding AbstractChecker abstract class. You need to implement run() method.

public abstract List<CheckResult> run() throws IOException, JFlexException;

This method should return content the value returned by getCheckResults() which is a list built by calling setError() when your algorithm finds some issue on the file given by getInputFile().

public List<CheckResult> getCheckResults();
protected void setError(final String pLocation, final String pMessage, final int pLine);
public File getInputFile();

Do not override name, id, languageId and inputFile: they will be automatically built by i-Code framework through information you gave in xml rules definition file.

3.2. Using LEX

For writing some lex checkers you can find examples here:

And to build your lex simply add these lines into your pom.xml.

<!-- generate the lex code -->
<build>
    <plugins>
        <plugin>
            <groupId>de.jflex</groupId>
            <artifactId>jflex-maven-plugin</artifactId>
            <version>${jflex.version}</version>
            <executions>
                <execution>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                    <configuration>
                        <minimize>true</minimize>
                        <lexDefinitions>
                            <lexDefinition>src/main/resources/lex</lexDefinition>
                        </lexDefinitions>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

This code assumes your lex files lay in src/main/resources/lex.

4. Expose your checkers

In the project's resources, add an xml file containing checkers definition.

<?xml version="1.0" encoding="UTF-8"?>
<checkers>
    <check
            class="fr.cnes.icode.mycustomplugin.checkers.NoToDo"
            id="fr.cnes.icode.mycustomplugin.checkers.NoToDo"
            name="No TODO in comment"
            languageId="fr.cnes.icode.mycustomlanguage">
    </check>
    <check
            class="fr.cnes.icode.mycustomplugin.checkers.NoFloat"
            id="fr.cnes.icode.mycustomplugin.checkers.NoFloat"
            name="No floating number should be used"
            languageId="fr.cnes.icode.mycustomlanguage">
    </check>
</checkers>

Attribute class shall point to the real implementation class of the checker but attribute id can be set to any unique value.

Then, override CheckersDefinition class to declare your checkers in code by adding your XML definition through addFromResources() method.

package fr.cnes.icode.mycustomplugin.checkers;

import fr.cnes.icode.data.CheckersDefinition;

/**
 * Define checkers to be supported by this plugin.
 */
public class MyCustomCheckersDefinition extends CheckersDefinition {

    /**
     * Override this method to inject checkers in containers field.
     */
    @Override
    public void define() {
        addFromResources("/my-custom-checkers.xml");
    }
}

If your xml definition file is in the root of resources directory and is named my-custom-checkers.xml, you should be able to import it in define() thanks to this instruction: addFromResources("/my-custom-checkers.xml");.

5. Test your plugin

Testing your plugin is as easy as copy & paste this generic test class. You just have to provide information about code used to test your plugin:

  • Path of the file with error(s)
  • Path of the file without error
  • List of ordered line numbers containing errors
  • List of ordered errors' location (function/scope)
  • Name of the class to test
package fr.cnes.icode.c.checkers;

import fr.cnes.icode.test.ICodeCheckerTester;

public class TestAllCCheckers implements ICodeCheckerTester {

    public static Object[][] data() {
        return new Object[][]{
                {"/PRIMITIVE_TYPE/error.c", "/PRIMITIVE_TYPE/no_error.c", new int[]{72}, new String[]{"GLOBAL"}, NoFloat.class},
                {"/FORBIDDEN_KEYWORDS/error.c", "/FORBIDDEN_KEYWORDS/no_error.c", new int[]{60, 62, 65, 71}, new String[]{"GLOBAL", "GLOBAL", "GLOBAL", "GLOBAL"}, NoToDo.class}
        };
    }

}

This test method uses parametric unit tests with JUnit 5.