Skip to content
Zachary Moore edited this page Jan 22, 2019 · 2 revisions

Usage

When creating and running lint rules there is a flow of classes to generate in order to create the rule.

The classes are:

LintImplementation<T> - Target WrappedObject implementing class type, determine rules for failure, configure output

LintRule.BuilderLintRule - Configure severity, set issue ID, explanation, description, and implementation.

LintRegister - Register all LintRules

LintRunner - Pass in LintRegister and configure directories or files to be checked with registry's issues

ReportRunner - Pass in LintRunner and generate HTML report

WrappedObject

WrappedObject is an interface that 3 of our core classes implement.
This interface allows us to have more context about the objects we look at when analyzing them for linting.

The interface provides 4 methods:

  • getOriginatingKey() - returns the closest JSONObject key associated with this Object. If there is no immediate key it will travel up the chain until one is found. Only the root JSONObject will have a null return
  • getParentObject() - returns the parent WrappedObject that created this Object. Only the root JSONObject will have a null return
  • parseAndReplaceWithWrappers() - void method that will parse the sub objects of this Object and replace them with WrappedObjects.
  • isPrimitive() - returns true if the Object is simply a wrapper around a primitive value

In the library we have 3 WrappedObject implementing classes:

  • JSONObject - A wrapper around the JSON-java JSONObject that @Overrides the toMap() to return this library's objects
  • JSONArray - A wrapper around the JSON-java JSONArray that @Overrides the toList() and toJSONObject() to return this library's objects
  • WrappedPrimitive<T> - A wrapper around all other datatypes in java in order to provide extra context in terms of the JSON File. This class has a getValue() method to return the original object it was generated from.

LintImplementation

LintImplementation is the core of the library.
LintImplementation is an abstract class with 3 abstract methods and a type generic.
LintImplementation takes in a type generic which must be one of the 3 provided classes that implement WrappedObject.

LintImplementation has 4 methods and an instance variable:

  • private String reportMessage - the message that will be reported when this implementation catches a lint error. This String can be set at runtime or ignored and overwrote with report(T t)
  • getClazz() - returns the target class to be analyzed. If using WrappedPrimitive<T> must return T.class else must return JSONArray or JSONObject
  • shouldReport(T t) - the main function of the class. This is where your LintRule will either catch an error or not. Every instance of the <T> of your LintImlpementation will run through this method. This is where you should apply your Lint logic and decide whether or not to report
  • report(T t) - funtion to return reportMessage or be overwrote and return a more static string
  • setReportMessage() - manually set the reportMessage string in the class (usually during shouldReport()) to provide more detail in the lint report

Note: If a reportMessage is not set when report() is called a NoReportSetException will be thrown.

WrappedPrimitive Caveats

When working with LintImplementation and WrappedPrimitive you must create your LintImplementation of type WrappedPrimitive<T> such as

    new LintImplementatioin<WrappedPrimitive<Integer>>()

However when writing your getClazz() method you must return the inner class of the WrappedPrimitive.

For Example:
Bad

LintImplementation<WrappedPrimitive<String>> lintImplementation ...
...
  Class<T> getClazz() {
      return WrappedPrimitive.class;
  }
...

Good

LintImplementation<WrappedPrimitive<String>> lintImplementation ...
...
Class<T> getClazz() {
    return String.class;
}

Writing your shouldReport

When writing your shouldReport for a LintImplementation you have access to a lot of helper methods to assist in navigating the JSON File.

A list of existing helper methods available from BaseJSONAnalyzer are:

  protected boolean hasKeyAndValueEqualTo(JSONObject jsonObject, String key, Object toCheck);

  protected boolean hasIndexAndValueEqualTo(JSONArray jsonArray, int index, Object toCheck);

  protected WrappedPrimitive safeGetWrappedPrimitive(JSONObject jsonObject, String key);

  protected  JSONObject safeGetJSONObject(JSONObject jsonObject, String key);

  protected JSONArray safeGetJSONArray(JSONObject jsonObject, String key);

  protected WrappedPrimitive safeGetWrappedPrimitive(JSONArray array, int index);

  protected JSONObject safeGetJSONObject(JSONArray array, int index);

  protected JSONArray safeGetJSONArray(JSONArray array, int index);

  protected <T> boolean isEqualTo(WrappedPrimitive<T> wrappedPrimitive, T toCheck);

  protected boolean isOriginatingKeyEqualTo(WrappedObject object, String toCheck);

  protected <T> boolean isType(Object object, Class<T> clazz);

  protected <T> boolean isParentOfType(WrappedObject object, Class<T> clazz);

  protected boolean reduceBooleans(Boolean... booleans);

Output from report

There are 2 ways to set your reportMessage:

  1. @Override the report() method.
  2. setReportMessage() in the shouldReport() and have more dynmic report messages

LintRule

LintRule is our class we use to setup what triggers a failure for a lint rule as well as what will happen when we have a failure.

LineRule can only be created with LintRule.Builder and can not be directly instantiated.

A LintRule can have the following properties set through the builder:

  • LintLevel level (REQUIRED) - can be IGNORE, WARNING, ERROR and signals severity of Lint Rule
  • LintImplementation implementation (REQUIRED) - LintImplementation conigured to determine when this lint rule should report issues
  • String issueId (REQUIRED) - Name of this lint rule. Must be unique.
  • String issueDescription - Short description of this lint rule.
  • String issueExplanation - More in-depth description of lint rule.

Note: If the required fields are not set when LintRule.Builder.build() is called a LintRuleBuilderException will be thrown.

LintRegister

LintRegister is a simple class to register as many or as few LintRules as wanted.

Our only method is

    register(LintRule ...toRegister)

which will register LintRules.

Our LintRegister acts as a simple intermediate between non IO parts of the Lint stack and our IO parts of our Lint stack, the LintRunner

LintRunner

LintRunner is our class that takes in a LintRegister and String basePath to load files from.

This class has a

    public Map<LintRule, Map<JSONFile, List<String>>> lint()

method which will lint our files for us but usually is just used as an intermediate class between our linting stack and reporting stack.

When calling lint() LintRunner will internally store the result for later analysis in

    public int analyzeLintAndGiveExitCode() 

analyzeLintAndGiveExitCode() will analyze the interal lint representation and return eithe a 0 or 1, the latter indicating a lint failure.

This method is called at the end of ReportRunner's report() method.

ReportRunner

ReportRunner is the entrypoint to our Reporting stack and the end point of our linting library.

The class takes in a LintRunner to connect and interact with our Linting stack.

The class also has a

    public void report(String outputPath);

method which will generate an html report of all the lint errors in the given path as supplied by the LintRunner as well as call System.exit() based on the LintRunner's analysis of whether we passed our lint or not.

Build Integration

In this repo we implemented a gradle task to be able to be tied into any build integration we want to do with our project.

All that needs to happen is a new repo needs to be created with your custom linting rules, a main needs to tie it all together, and a gradle task has to hit the main.

Since our ReportRunner class handles exit codes automatically for us, we can simply tie this build task however we want into our pipeline and we will either fail or succeed based on our lint status.

Tying into existing repos

When trying to hook up to existing repos we can take 2 approaches:

  1. Make lint rules in an existing project that holds our json files
  2. Make a separate library to hold our json lint rules, import into an existing project, and set up a build integration from there.

Clone this wiki locally