Skip to content

Writing A Rule

Jon Schneider edited this page May 11, 2016 · 3 revisions

The Rule implementation

Lint rules are AST visiting rules, because the AST gives us the ingredients we need to form good auto-fixing strategies. This will become apparent momentarily. In this example, we are using the optional GradleModelAware feature to imbue our visitor with knowledge of the evaluated Gradle Project object. The combination of Groovy AST and the evaluated model allows us to write powerful rules.

class FixJerseyBundleRule extends GradleLintRule {
  String description = 'remove fix-jersey-bundle plugin when it has no effect'

  @Override
  void visitApplyPlugin(MethodCallExpression call, String plugin) {
      if(plugin == 'nebula.fix-jersey-bundle') {
        def foundJerseyBundle = false
        def deps = project.configurations*.resolvedConfiguration*.firstLevelModuleDependencies.flatten()
        while(!deps.isEmpty() && !foundJerseyBundle) {
            foundJerseyBundle = deps.any { it.name == 'jersey-bundle' }
            deps = deps*.children.flatten()
        }

        if(!foundJerseyBundle)
          addBuildLintViolation('since there is no dependency on jersey-bundle this plugin has no effect', call)
            .delete(call) // Note: we could keep chaining additional fixes here if there was more to do
      }
  }
}

We use the AST to look for the specific piece of code where nebula.fix-jersey-bundle was applied. We could determine through the Gradle model that the plugin had been applied, but not how or where this had been accomplished. Then we transition to using the Gradle model to determine if jersey-bundle is in our transitive dependencies. We could not have determined this with the AST alone! Also, since linting runs not only after project configuration but in fact LAST in the task execution order, we can comfortably use Gradle bits like resolvedConfiguration without fear of introducing side effects (like resolving a configuration early that will later be touched by another plugin).

As shown above, addBuildLintViolation is used to indicate to the lint plugin that this block of code applying nebula.fix-jersey-bundle violates the rule, and the delete fix hint tells fixGradleLint that it can safely delete this code snippet.

Finally, notice how we overrode the visitApplyPlugin method. GradleLintRule implements the GradleAstVisitor interface which adds several convenience hooks for Gradle specific constructs to the rich set of hooks already provided by CodeNarc's AbstractAstVisitor, including:

  • visitApplyPlugin(MethodCallExpression call, String plugin)
  • visitExtensionProperty(ExpressionStatement expression, String extension, String prop, String value)
  • visitExtensionProperty(ExpressionStatement expression, String extension, String prop)
  • visitGradleDependency(MethodCallExpression call, String conf, GradleDependency dep)
  • visitConfigurationExclude(MethodCallExpression call, String conf, GradleDependency exclude)
  • visitDependencies()

Lint violation fixes

There are several lint violation fixes currently available. For AST-based fixes, you would typically want to use insertAfter, insertBefore, replaceWith, or delete. For fixes that cannot specify AST nodes, such as text files, replaceAll, deleteLines, deleteFile and createFile are also available.

The properties file

Lastly, provide a properties file that ties the Rule implementation to a short name that is used to apply the rule through the gradleLint.rules extension property. For our example, add a properties file: META-INF/lint-rules/fix-jersey-bundle.properties'. Any jar that provides properties files in META-INF/lint-rules` and is on the buildscript classpath effectively contributes usable rules to the plugin.

Our properties file contains a single line:

implementation-class=org.example.gradle.lint.FixJerseyBundleRule