Skip to content

Commit

Permalink
Bypassed forces rule - for version constraint with strict version
Browse files Browse the repository at this point in the history
  • Loading branch information
OdysseusLives committed Apr 2, 2020
1 parent da36a03 commit 03ebc75
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 10 deletions.
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
Expand Up @@ -30,7 +30,6 @@ class BypassedForcesWithResolutionRulesSpec extends IntegrationTestKitSpec {
def setup() {
setupProjectAndDependencies()
debug = true
forwardOutput = true
}

@Unroll
Expand Down Expand Up @@ -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')
Expand Down

0 comments on commit 03ebc75

Please sign in to comment.