How to Add a New Service-Lint Rule

This guide walks you through creating, documenting, registering, and wiring a new Service-Lint rule so it appears in the docs and is executed by the engine.

Overview

To add a rule, you will:

  1. Create a new class that implements gov.va.mobile.tools.servicelint.rule.Rule.

  2. Annotate the rule with documentation metadata and provide good/bad examples.

  3. Register the rule via Java Service Provider Interface (SPI) in META-INF/services/gov.va.mobile.tools.servicelint.rule.Rule.

  4. Ensure the rule is included in DefaultRuleEngineProvider (if applicable in your version) or is discoverable by the engine.

  5. Add a rule documentation page (SLxxx.adoc) if your repo uses individual pages per rule.

1) Create a Rule Class

Create your rule in the service-lint-rules module under an appropriate package, for example:

package gov.va.mobile.tools.servicelint.rules;

import gov.va.mobile.tools.servicelint.model.*; // Adjust as needed for your project
import gov.va.mobile.tools.servicelint.rule.Rule;
import gov.va.mobile.tools.servicelint.rule.RuleContext;
import gov.va.mobile.tools.servicelint.rule.Severity; // if applicable

/**
 * Example rule that validates something in a repo.
 */
public class SL999ExampleRule implements Rule {

    @Override
    public String getId() {
        return "SL999"; // Unique ID following the SLxxx pattern
    }

    @Override
    public String getTitle() {
        return "Example Validation"; // Short title used in lists
    }

    @Override
    public String getDescription() {
        return "Explains what the rule checks and why it matters.";
    }

    @Override
    public Severity getSeverity() {
        return Severity.INFO; // Or WARNING/ERROR depending on impact
    }

    @Override
    public RuleResult evaluate(RuleContext ctx) {
        // Implement your logic; return pass/fail with messages & remediation
        boolean ok = true; // replace with actual check
        if (ok) {
            return RuleResult.pass(getId());
        } else {
            return RuleResult.fail(getId(), "Found an issue", "How to fix it");
        }
    }
}

Tips:

  • Keep logic deterministic and fast; avoid network calls where possible.

  • Prefer small, focused rules.

  • Use existing utilities from the project if available (path scanning, YAML/JSON helpers, Maven POM helpers, etc.).

2) Add Annotations and Inline Docs

Many rules embed documentation in annotations to auto-generate docs. If your project has such annotations, add them to your class.

For example (adjust annotation names to match your project):

@RuleDoc(
    id = "SL999",
    title = "Example Validation",
    severity = Severity.INFO,
    since = "1.0.7",
    summary = "Validates example condition.",
    rationale = "Explain why this is important.",
    remediation = "Explain how to resolve violations."
)
@RuleExamples(
    good = {
        @Example(title = "Good: compliant file", code = """
# some.yaml
key: expected
"""),
    },
    bad = {
        @Example(title = "Bad: non-compliant file", code = """
# some.yaml
key: unexpected
""")
    }
)
public class SL999ExampleRule implements Rule { /* ... */ }

Notes:

  • If your codebase doesn’t have these annotations yet, you can skip them, but prefer adding JavaDoc on methods to describe behavior and examples.

  • Use triple-quoted strings or escaping for multi-line example snippets. If your Java version doesn’t support text blocks, store examples as resource files and reference them in documentation.

3) Provide Good/Bad Examples

Good/Bad examples help users quickly understand violations.

Options:

  • Inline via annotations (shown above).

  • As resource files under service-lint-rules/src/test/resources/rules/SL999/ and reference them from your docs.

Example file layout:

service-lint-rules/
  src/test/resources/rules/SL999/
    good-example-1.yaml
    bad-example-1.yaml

4) Register the Rule with Java SPI

Add your fully-qualified rule class name to the service provider file so the engine can find it via ServiceLoader.

File: service-lint-rules/src/main/resources/META-INF/services/gov.va.mobile.tools.servicelint.rule.Rule

Append a new line with your class name:

# Existing rules...

gov.va.mobile.tools.servicelint.rules.SL999ExampleRule

Guidelines:

  • One class per line, no trailing spaces.

  • Keep the list sorted by ID if that’s the existing convention.

5) Ensure the Rule is Wired into the Engine

Depending on your version, the engine may rely on ServiceLoader or hard-coded registration in DefaultRuleEngineProvider.

  • If ServiceLoader is used, updating the SPI file may be sufficient.

  • If rules are explicitly added in DefaultRuleEngineProvider, add your rule there.

Example (simplified):

package gov.va.mobile.tools.servicelint.engine;

import gov.va.mobile.tools.servicelint.rule.Rule;
import gov.va.mobile.tools.servicelint.rules.SL999ExampleRule;

public class DefaultRuleEngineProvider implements RuleEngineProvider {
    @Override
    public RuleEngine getRuleEngine() {
        RuleEngine engine = new RuleEngine();
        // If not using ServiceLoader, register explicitly:
        engine.register(new SL999ExampleRule());
        return engine;
    }
}

Check your current implementation of DefaultRuleEngineProvider and follow the prevailing pattern.

6) Add a Documentation Page for the Rule (SLxxx)

If the project keeps one page per rule (e.g., SL009.adoc, SL010.adoc exist), add a page for your new rule, such as SL999.adoc.

File: service-lint/docs/modules/ROOT/pages/SL999.adoc

= SL999: Example Validation
:description: Validates example condition.
:severity: INFO
:since: 1.0.7

== Summary
Validates example condition across the repository.

== Rationale
Explain why the rule exists.

== Good example
[source,yaml]

key: expected

== Bad example
[source,yaml]

key: unexpected

== Remediation
Explain how to resolve the violation and provide tips.

Finally, add the page to the navigation if the project maintains a manual nav list (see next step).

7) Update the Docs Navigation

Update service-lint/docs/modules/ROOT/nav.adoc to include your new rule page and/or this guide.

  • To add this guide to the navigation, add a new entry, for example under Rules or a new Contributing section:

* xref:index.adoc[Rules]
** xref:SL001.adoc[SL001: Service Lint Version Up-to-date]
...
* xref:adding-a-new-rule.adoc[How to Add a New Rule]
  • To add your specific rule page, add an item to the Rules list in nav.adoc in the correct numerical order.

8) Test Locally

  • Run any available docs generation to ensure the new pages render correctly (Antora or build tooling).

  • Execute service-lint in a test repository to verify the rule loads (check logs for SL999). If the engine uses ServiceLoader, ensure the SPI file contains your class and is packaged correctly.

9) Submit Your Change

  • Include the new Java class, tests (if applicable), SPI update, DefaultRuleEngineProvider update, and documentation changes in your PR.

  • Add notes to CHANGELOG.md if required by the project.