From 03ebc75b9dc4b7940a8059d23f4339709ddb6fd6 Mon Sep 17 00:00:00 2001 From: Aubrey Chipman Date: Thu, 2 Apr 2020 14:26:45 -0700 Subject: [PATCH] Bypassed forces rule - for version constraint with strict version --- .../rule/dependency/BypassedForcesRule.groovy | 46 ++++++++++--- ...passedForcesWithResolutionRulesSpec.groovy | 66 ++++++++++++++++++- 2 files changed, 102 insertions(+), 10 deletions(-) diff --git a/src/main/groovy/com/netflix/nebula/lint/rule/dependency/BypassedForcesRule.groovy b/src/main/groovy/com/netflix/nebula/lint/rule/dependency/BypassedForcesRule.groovy index aab12360..576e4d43 100644 --- a/src/main/groovy/com/netflix/nebula/lint/rule/dependency/BypassedForcesRule.groovy +++ b/src/main/groovy/com/netflix/nebula/lint/rule/dependency/BypassedForcesRule.groovy @@ -7,8 +7,10 @@ import groovy.transform.CompileStatic import org.codehaus.groovy.ast.ClassNode import org.codehaus.groovy.ast.expr.BinaryExpression import org.codehaus.groovy.ast.expr.ClosureExpression +import org.codehaus.groovy.ast.expr.ConstantExpression import org.codehaus.groovy.ast.expr.Expression import org.codehaus.groovy.ast.expr.MethodCallExpression +import org.codehaus.groovy.ast.stmt.BlockStatement import org.gradle.api.artifacts.Configuration class BypassedForcesRule extends GradleLintRule implements GradleModelAware { @@ -30,23 +32,49 @@ class BypassedForcesRule extends GradleLintRule implements GradleModelAware { @Override void visitAnyGradleDependency(MethodCallExpression call, String conf, GradleDependency dep) { - if(!call.arguments.metaClass.getMetaMethod('getExpressions')) { + if (!call.arguments.metaClass.getMetaMethod('getExpressions')) { return // short-circuit if there are no expressions } - if(!call.arguments.expressions - .findAll {it instanceof ClosureExpression} - .any { closureContainsForce(it as ClosureExpression) }){ - return // short-circuit if there are no forces + if (call.arguments.expressions + .findAll { it instanceof ClosureExpression } + .any { closureContainsForce(it as ClosureExpression) }) { + forcedDependencies.add(new ForcedDependency(dep, call, conf)) + } + + if (call.arguments.expressions + .findAll { it instanceof ClosureExpression } + .any { closureContainsVersionConstraintWithStrictVersion(it as ClosureExpression) }) { + forcedDependencies.add(new ForcedDependency(dep, call, conf)) } - forcedDependencies.add(new ForcedDependency(dep, call, conf)) } private static Boolean closureContainsForce(ClosureExpression expr) { - return expr.code.statements.any { (it.expression instanceof BinaryExpression) && - ((BinaryExpression)it.expression).leftExpression?.variable == 'force' && - ((BinaryExpression)it.expression).rightExpression?.value == true } + return expr.code.statements.any { + (it.expression instanceof BinaryExpression) && + ((BinaryExpression) it.expression).leftExpression?.variable == 'force' && + ((BinaryExpression) it.expression).rightExpression?.value == true + } + } + + private static Boolean closureContainsVersionConstraintWithStrictVersion(ClosureExpression expr) { + return expr.code.statements.any { st -> + (st.expression instanceof MethodCallExpression) && + ((MethodCallExpression) st.expression).arguments?.any { arg -> + arg instanceof ClosureExpression && + arg?.code instanceof BlockStatement && + arg?.code?.statements?.any { stmt -> + stmt?.expression instanceof MethodCallExpression && + stmt?.expression?.method instanceof ConstantExpression && + ((ConstantExpression) stmt?.expression?.method)?.value == 'strictly' && + stmt?.expression?.arguments?.expressions?.any { expre -> + expre instanceof ConstantExpression && + !expre?.value?.equals(null) + } + } + } + } } @CompileStatic diff --git a/src/test/groovy/com/netflix/nebula/lint/rule/dependency/BypassedForcesWithResolutionRulesSpec.groovy b/src/test/groovy/com/netflix/nebula/lint/rule/dependency/BypassedForcesWithResolutionRulesSpec.groovy index cc500ef6..9e4bc8fb 100644 --- a/src/test/groovy/com/netflix/nebula/lint/rule/dependency/BypassedForcesWithResolutionRulesSpec.groovy +++ b/src/test/groovy/com/netflix/nebula/lint/rule/dependency/BypassedForcesWithResolutionRulesSpec.groovy @@ -30,7 +30,6 @@ class BypassedForcesWithResolutionRulesSpec extends IntegrationTestKitSpec { def setup() { setupProjectAndDependencies() debug = true - forwardOutput = true } @Unroll @@ -170,6 +169,71 @@ class BypassedForcesWithResolutionRulesSpec extends IntegrationTestKitSpec { coreAlignment << [true] } + @Unroll + def 'dependency with strict version declaration honored | core alignment #coreAlignment'() { + buildFile << """\ + dependencies { + implementation('test.nebula:a:1.1.0') { + version { strictly '1.1.0' } + } + implementation 'test.nebula:b:1.0.0' // added for alignment + implementation 'test.nebula:c:1.0.0' // added for alignment + implementation 'test.other:z:1.0.0' // brings in bad version + } + """.stripIndent() + + when: + def tasks = ['dependencyInsight', '--dependency', 'test.nebula', "-Dnebula.features.coreAlignmentSupport=$coreAlignment"] + tasks += 'fixGradleLint' + def results = runTasks(*tasks) + + then: + // strictly rich version constraint to an okay version is the primary contributor + results.output.contains('test.nebula:a:{strictly 1.1.0} -> 1.1.0\n') + results.output.contains('test.nebula:a:1.2.0 -> 1.1.0\n') + results.output.contains('test.nebula:b:1.0.0 -> 1.1.0\n') + results.output.contains('test.nebula:c:1.0.0 -> 1.1.0\n') + + results.output.contains('- Forced') + results.output.contains 'aligned' + + results.output.contains('0 violations') + + where: + coreAlignment << [true] + } + + @Unroll + def 'dependency with strict version declaration not honored | core alignment #coreAlignment'() { + buildFile << """\ + dependencies { + implementation('test.nebula:a') { + version { strictly '1.2.0' } // strict to bad version + } + implementation 'test.nebula:b:1.0.0' // added for alignment + implementation 'test.nebula:c:1.0.0' // added for alignment + } + """.stripIndent() + + when: + def tasks = ['dependencyInsight', '--dependency', 'test.nebula', "-Dnebula.features.coreAlignmentSupport=$coreAlignment"] + tasks += 'fixGradleLint' + def results = runTasks(*tasks) + + then: + // substitution rule to a known-good-version is the primary contributor; rich version strictly constraint to a bad version is the secondary contributor + results.output.contains 'test.nebula:a:{strictly 1.2.0} -> 1.3.0' + results.output.contains 'test.nebula:b:1.0.0 -> 1.3.0' + results.output.contains 'test.nebula:c:1.0.0 -> 1.3.0' + + results.output.contains 'aligned' + + results.output.contains('This project contains lint violations.') + + where: + coreAlignment << [false, true] + } + void setupProjectAndDependencies() { def graph = new DependencyGraphBuilder() .addModule('test.nebula:a:1.0.0')