All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.nebula.lint.rule.dependency.UndeclaredDependencyRule.groovy Maven / Gradle / Ivy

Go to download

Pluggable and configurable linter tool for identifying and reporting on patterns of misuse or deprecations in Gradle scripts

There is a newer version: 20.2.2
Show newest version
package com.netflix.nebula.lint.rule.dependency

import com.netflix.nebula.lint.SourceSetUtils
import com.netflix.nebula.lint.rule.GradleLintRule
import com.netflix.nebula.lint.rule.GradleModelAware
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.expr.MethodCallExpression
import org.gradle.api.artifacts.ModuleVersionIdentifier

@CompileStatic
class UndeclaredDependencyRule extends GradleLintRule implements GradleModelAware {
    private static final String DEPENDENCIES_BLOCK = 'rootDependenciesBlock'
    String description = 'Ensure that directly used transitives are declared as first order dependencies'
    DependencyService dependencyService

    @Override
    protected void beforeApplyTo() {
        dependencyService = DependencyService.forProject(project)
    }

    @Override
    void visitDependencies(MethodCallExpression call) {
        def parentNode = parentNode()
        if (parentNode == null) {
            //bookmark only current project dependencies (not allprojects and subprojects)
            bookmark(DEPENDENCIES_BLOCK, call)
        }
    }

    @Override
    void visitClassComplete(ClassNode node) {
        Set insertedDependencies = [] as Set
        Map> violations = new HashMap()

        if (SourceSetUtils.hasSourceSets(project)) {
            // sort the sourceSets from least dependent to most dependent, e.g. [main, test, integTest]
            def sortedSourceSets = SourceSetUtils.getSourceSets(project).sort(false, dependencyService.sourceSetComparator())

            sortedSourceSets.each { sourceSet ->
                def confName = sourceSet.compileClasspathConfigurationName
                violations.put(confName, new HashMap())

                def undeclaredDependencies = dependencyService.undeclaredDependencies(confName)
                def filteredUndeclaredDependencies = filterUndeclaredDependencies(undeclaredDependencies, confName)

                if (!filteredUndeclaredDependencies.isEmpty()) {
                    def dependencyBlock = bookmark(DEPENDENCIES_BLOCK)
                    if (dependencyBlock != null) {
                        filteredUndeclaredDependencies.each { undeclared ->
                            // only add the dependency in the lowest configuration that requires it
                            if (insertedDependencies.add(undeclared)) {
                                // collect violations for handling later
                                HashMap violationsForConfig = violations.get(confName)
                                violationsForConfig.put(undeclared.toString(), dependencyBlock)
                                violations.put(confName, violationsForConfig)
                            }
                        }
                    } else {
                        // there is no dependencies block so we need a new one
                        addBuildLintViolation("one or more classes are required by your code directly, " +
                                "and you require a dependencies block in your subproject $project")
                                .insertAfter(project.buildFile, 0, """\
                                    dependencies {
                                    }
                                    """.stripIndent())
                    }
                }
            }
        }

        addUndeclaredDependenciesAlphabetically(violations)
    }

    Set filterUndeclaredDependencies(Set undeclaredDependencies, String configurationName) {
        return undeclaredDependencies
    }

    @CompileDynamic
    private void addUndeclaredDependenciesAlphabetically(Map> violations) {
        TreeMap sortedViolations = new TreeMap()
        sortedViolations.putAll(violations)

        for (Map.Entry> entry : sortedViolations.entrySet()) {
            String configurationName = entry.getKey()
            TreeMap sortedDependenciesAndBlocks = new TreeMap()
            sortedDependenciesAndBlocks.putAll(entry.getValue())

            for (Map.Entry dependencyAndBlock : sortedDependenciesAndBlocks.entrySet()) {
                String undeclaredDependency = dependencyAndBlock.getKey()
                ASTNode dependencyBlock = dependencyAndBlock.getValue()

                addBuildLintViolation("one or more classes in $undeclaredDependency are required by your code directly")
                        .insertIntoClosureAtTheEnd(dependencyBlock, "${declarationConfigurationName(configurationName)} '$undeclaredDependency'")
            }
        }
    }

    private static String declarationConfigurationName(String configName) {
        return configName
                .replace('compileClasspath', 'implementation')
                .replace('CompileClasspath', 'Implementation')
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy