How to Write a Custom Check

Posted on 02/15/2017 by Elijah Ezaga

The Custom Check Framework

There are a number of code checking tools online that assist development teams in writing good code e.g. ESLint and FxCop. However, some situations may require that certain kind of code checks are not available in existing tools be used. Teamscale solves this problem through its Custom Check Framework. Additionally, Teamscale can perform the checks for code histories and help manage the findings resulting from the checks.

The Custom Check Framework makes it possible to write code checks that can spot problem areas in code, thus enabling Teamscale promote writing of high quality software and encouraging adherence to best coding practices. Compared with other kinds of analyses Teamscale runs, custom checking is achieved through a simpler mechanism promoting efficiency. The simplicity is also reflected in the ease with which new checks can be developed and tested for various languages.

On an implementation level, a custom check is a Java class that selects code entities e.g. methods, class fields or comments and reports findings about them. These findings are collected and accessible in the findings perspective of a running Teamscale web instance.

Aside from custom checking, the custom check framework is equipped to perform preprocessing, token scanning, creation of shallow entities and type tracking for a large array of programming languages including Java, C++, Python, PHP and ABAP.

Framework Organization

Implementation of the framework is located in the eu.cqse.check Teamscale Eclipse project. The structure follows:

  1. Core classes are found in the eu.cqse.check.framework.core package. It contains the very basic requirements for writing a check. It is important to note that the parent of all check classes is the CheckImplementationBase class. Other content classes in this package will be discussed later in this post.

  2. Base classes in eu.cqse.check.base consist of implementations that address issues about specific shallow entities. For instance the CommentCheckBase and UnwantedMethodCallsCheckBase are base classes for checks about comments and unwanted method calls respectively. In writing a check, its easier to extend one of these base classes than the CheckImplementationBase class.

  3. The eu.cqse.check.{language} packages contains check implementations for specific languages. For instance eu.cqse.check.cs for C# related checks.

In addition, other packages that perform language-specific functions important to the framework include

  1. eu.cqse.check.framework.preprocessor.* : preprocessor implementations.
  2. eu.cqse.check.framework.scanner.* : classes that perform token scanning of code files.
  3. eu.cqse.check.framework.shallowparser.languages.* : shallow parsers providing entities.
  4. eu.cqse.check.framework.typetracker.* : type tracking features.

Implementing a Custom Check

Deciding a Base Class

It can be rare to start writing a new custom check from scratch. There are quite a lot of existing checks that can be reused. Therefore, having gained good understanding of what a check should do, one of the first things to look for is existing reusable checks. Aforementioned already, these can be found in the language-specific check folders and eu.cqse.check.base. If existing checks cannot be reused, then extend the CheckImplementationBase class overriding at least the execute() method.

Defining Check Information & Parametrization

Annotate a check class with the ACheck annotation in order to provide its name and description to Teamscale. The annotation class is found in the eu.cqse.check.framework.core package together with classes that can define other useful information presented below:

  1. Group Name: check findings can be classified as bad practice or security-related. Enumeration values are in CheckGroupName.

  2. Languages: a list of languages that this check applies to. The ELanguage enumeration defines possible values.

  3. Parameters: a list of values of type ECheckParameter. Use ABSTRACT_SYNTAX_TREE if you need to access a list of shallow entities of source code. This comes from context.getAbstractSyntaxTree(). Use TYPE_RESOLUTION if you need to access type information about variables. Then use context().getTypeResolution() for access.

  4. Default Enablement: a value of type EFindingEnablement that indicates the severity of a finding. RED indicates a severe code issue while YELLOW means an issue is less severe. A finding check can also be turned OFF or AUTO-enabled so that some other process determines its enablement state.

Using custom check options, checks can be parametrized to allow users to pass numeric or string input into them. For instance, if you want code files to have a particular company header comment, such comment could be provided into the check using this feature. First furnish a check class field with the ACheckOption annotation and then pass in some desired data (through Projects perspective > Quality Indicators) before running analysis in Teamscale.

Check Logic

A common task is to select only certain kinds of shallow entities. The select() method takes an XPATH string that describes interested entites or sub-entities, returning them in a list. Shallow entity types include modules, methods and attributes. A complete list of them are found in eu.cqse.check.framework.shallowparser.framework.EShallowEntityTypes. Functions helpful for more specific description of tokens in XPath strings are also available e.g. The AttributeEqualSelectionFunctionBase is a base class for selecting attribute shallow entities whose name equals some given input. All selector functions inherit from the eu.cqse.check.framework.shallowparser.framework.FunctionBase class.

For every occurrence of a code quality issue, findings need to be created. Use the createFinding...() methods.

Testing Checks

Suppose we need to test a C# check that we’ve implemented in a java class MyCoolCSCheck.java,

  1. add one or more test source files with their result expectations into the folder test-data/eu.cqse.check/cs/MyCoolCSCheck. Every language has a folder under the test-data/eu.cqse.check folder. A sample source file »TestMyCoolCSCheck.cs« should have a result file as »TestMyCoolCSCheck.cs.expected«. The contents of the result file indicates the lines and offsets where a finding is located in the source file. To get the contents easily, add an empty result file and run the check test as described in step 2.

  2. Run test-src/eu.cqse.check.CheckTest class as a JUnit test.

If the check is parametrized, additionally provide a »TestMyCoolCSCheck.cs.parameters« file in the same folder as the source and result files. Specify the values of each check option parameter on separate lines in the format »parameter_name: paramter_values« without the quotes. The parameter name must match the value of the name attribute of the ACheckOption on the parameter.

Conclusion

Starting from Teamscale 2.0, it is now possible to deploy newly developed custom check implementations with a Teamscale application. This provides a sure way of helping developers extend the framework's features for writing better code.