Skip to content

Commit

Permalink
Make subproject lint and correction tasks defer to the root project s…
Browse files Browse the repository at this point in the history
…o that results appear at the end
  • Loading branch information
jkschneider committed Feb 2, 2016
1 parent 9a6096c commit f07d95b
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 141 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ dependencies {

compile 'org.codenarc:CodeNarc:latest.release'

// these two dependencies exist so we can provide a test harness
// to proprietary rule implementations packed in other jars
provided ('org.spockframework:spock-core:latest.release') {
exclude module: 'groovy-all'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,62 @@ class GradleLintPluginSpec extends IntegrationSpec {
""".toString()
}

def 'auto correct all violations on a multi-module project'() {
when:
buildFile.text = """
allprojects {
apply plugin: ${GradleLintPlugin.name}
gradleLint.rules = ['dependency-parentheses', 'dependency-tuple']
}
subprojects {
apply plugin: 'java'
dependencies {
compile('com.google.guava:guava:18.0')
}
}
"""

def subDir = addSubproject('sub', """
dependencies {
testCompile group: 'junit',
name: 'junit',
version: '4.11'
}
task taskA {}
""")

then:
def results = runTasksSuccessfully(':sub:fixGradleLint') // prove that this links to the root project task
println results.standardOutput

buildFile.text == """
allprojects {
apply plugin: ${GradleLintPlugin.name}
gradleLint.rules = ['dependency-parentheses', 'dependency-tuple']
}
subprojects {
apply plugin: 'java'
dependencies {
compile 'com.google.guava:guava:18.0'
}
}
""".toString()

new File(subDir, 'build.gradle').text == """
dependencies {
testCompile 'junit:junit:4.11'
}
task taskA {}
"""
}

def 'rules relative to each project'() {
when:
buildFile << """
buildFile.text = """
allprojects {
apply plugin: ${GradleLintPlugin.name}
gradleLint.rules = ['dependency-parentheses', 'dependency-tuple']
Expand Down Expand Up @@ -110,6 +163,7 @@ class GradleLintPluginSpec extends IntegrationSpec {

when:
def console = results.standardOutput.readLines()
println results.standardOutput

then:
console.findAll { it.startsWith('warning') }.size() == 2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.netflix.nebula.lint.rule.GradleViolation
import org.codenarc.rule.Rule
import org.codenarc.rule.Violation
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
import org.gradle.logging.StyledTextOutput
import org.gradle.logging.StyledTextOutputFactory
Expand All @@ -18,102 +19,97 @@ class GradleLintCorrectionTask extends DefaultTask {
null // see http://gradle.1045684.n5.nabble.com/injecting-dependencies-into-task-instances-td5712637.html
}

/**
* Clean up any ugly artifacts we may have created through the auto-fix process
* (e.g. empty extension object closures that have had all their property definitions removed)
*/
void postProcess() {
def ruleSet = RuleSetFactory.configureRuleSet([new EmptyClosureRule()])
void performCorrections(Project p, CorrectableStringSourceAnalyzer analyzer) {
p.buildFile.text = analyzer.corrected // perform initial correction

def analyzer = new CorrectableStringSourceAnalyzer(project.buildFile.text)
analyzer.analyze(ruleSet)
// Clean up any ugly artifacts we may have created through the auto-fix process
// (e.g. empty extension object closures that have had all their property definitions removed)
def ruleSet = RuleSetFactory.configureRuleSet([new EmptyClosureRule()])
def postProcessor = new CorrectableStringSourceAnalyzer(p.buildFile.text)
postProcessor.analyze(ruleSet)

project.buildFile.newWriter().withWriter { w ->
w << analyzer.corrected
}
p.buildFile.text = postProcessor.corrected
}

@TaskAction
void lintCorrections() {
if(!project.buildFile.exists()) {
return
}

def registry = new LintRuleRegistry(project)
def ruleSet = RuleSetFactory.configureRuleSet(project.extensions
.getByType(GradleLintExtension)
.rules
.collect { registry.buildRules(it) }
.flatten() as List<Rule>)

// look at org.gradle.logging.internal.DefaultColorMap
def textOutput = textOutputFactory.create('lint')
def registry = new LintRuleRegistry()

def analyzer = new CorrectableStringSourceAnalyzer(project.buildFile.text)
def results = analyzer.analyze(ruleSet)
def violationsByProject = [:]

if(results.violations.isEmpty()) {
textOutput.style(StyledTextOutput.Style.Identifier).println("Passed lint check with 0 violations; no corrections necessary")
return
}
([project] + project.subprojects).each { p ->
if (p.buildFile.exists()) {
def ruleSet = RuleSetFactory.configureRuleSet(p.extensions
.getByType(GradleLintExtension)
.rules
.collect { registry.buildRules(it, p) }
.flatten() as List<Rule>)

// perform initial correction
project.buildFile.newWriter().withWriter { w ->
w << analyzer.corrected
}
postProcess()
def analyzer = new CorrectableStringSourceAnalyzer(p.buildFile.text)
def results = analyzer.analyze(ruleSet)

printReport(results.violations, textOutput)
}
performCorrections(p, analyzer)
violationsByProject[p] = results.violations
}
}

private List printReport(List<Violation> violations, textOutput) {
def allViolations = violationsByProject.values().flatten()
def correctedViolations = 0, uncorrectedViolations = 0
def buildFilePath = relPath(project.rootDir, project.buildFile).path

violations.eachWithIndex { v, i ->
def severity = v.rule.priority <= 3 ? 'warning' : 'error'

if (v instanceof GradleViolation && v.isFixable()) {
textOutput.withStyle(StyledTextOutput.Style.Identifier).text('fixed'.padRight(10))
} else {
textOutput.withStyle(StyledTextOutput.Style.Failure).text(severity.padRight(10))
}

textOutput.text(v.rule.ruleId.padRight(35))
textOutput.withStyle(StyledTextOutput.Style.Description).println(v.message)

textOutput.withStyle(StyledTextOutput.Style.UserInput).println(buildFilePath + ':' + v.lineNumber)
textOutput.println("$v.sourceLine")

if (v instanceof GradleViolation && v.isFixable()) {
if (v.replacement) {
textOutput.withStyle(StyledTextOutput.Style.UserInput).println('replaced with:')
textOutput.println(v.replacement)
correctedViolations++
} else if (v.deleteLine) {
textOutput.withStyle(StyledTextOutput.Style.UserInput).println("deleted line $v.deleteLine")
correctedViolations++
} else if (v.addition) {
textOutput.withStyle(StyledTextOutput.Style.UserInput).println("adding:")
textOutput.print(v.addition)
correctedViolations++
if(allViolations.isEmpty()) {
textOutput.style(StyledTextOutput.Style.Identifier).println("Passed lint check with 0 violations; no corrections necessary")
} else {
textOutput.withStyle(StyledTextOutput.Style.UserInput).text('\nThis project contains lint violations. ')
textOutput.println('A complete listing of my attempt to fix them follows. Please review and commit the changes.\n')

violationsByProject.entrySet().each {
def buildFilePath = it.key.rootDir.toURI().relativize(it.key.buildFile.toURI()).toString()
def violations = it.value

violations.each { Violation v ->
def severity = v.rule.priority <= 3 ? 'warning' : 'error'

if (v instanceof GradleViolation && v.isFixable()) {
textOutput.withStyle(StyledTextOutput.Style.Identifier).text('fixed'.padRight(10))
} else {
textOutput.withStyle(StyledTextOutput.Style.Failure).text(severity.padRight(10))
}

textOutput.text(v.rule.ruleId.padRight(35))
textOutput.withStyle(StyledTextOutput.Style.Description).println(v.message)

textOutput.withStyle(StyledTextOutput.Style.UserInput).println(buildFilePath + ':' + v.lineNumber)
textOutput.println("$v.sourceLine")

if (v instanceof GradleViolation && v.isFixable()) {
if (v.replacement) {
textOutput.withStyle(StyledTextOutput.Style.UserInput).println('replaced with:')
textOutput.println(v.replacement)
correctedViolations++
} else if (v.deleteLine) {
textOutput.withStyle(StyledTextOutput.Style.UserInput).println("deleted line $v.deleteLine")
correctedViolations++
} else if (v.addition) {
textOutput.withStyle(StyledTextOutput.Style.UserInput).println("adding:")
textOutput.print(v.addition)
correctedViolations++
}
} else {
textOutput.withStyle(StyledTextOutput.Style.Error).println('\u2716 no auto-correct available')
uncorrectedViolations++
}

textOutput.println() // extra space between violations
}
} else {
textOutput.withStyle(StyledTextOutput.Style.Error).println('\u2716 no auto-correct available')
uncorrectedViolations++
}

textOutput.println() // extra space between violations
}

if(correctedViolations > 0)
textOutput.style(StyledTextOutput.Style.Identifier).println("Corrected $correctedViolations lint problems")
if(correctedViolations > 0)
textOutput.style(StyledTextOutput.Style.Identifier).println("Corrected $correctedViolations lint problems\n")

if(uncorrectedViolations > 0)
textOutput.style(StyledTextOutput.Style.Error).println("Corrected $correctedViolations lint problems")
}

private static File relPath(File root, File f) {
new File(root.toURI().relativize(f.toURI()).toString())
if(uncorrectedViolations > 0)
textOutput.style(StyledTextOutput.Style.Error).println("Corrected $correctedViolations lint problems\n")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,33 @@ import org.gradle.api.Project

class GradleLintPlugin implements Plugin<Project> {
private final exemptTasks = ['help', 'tasks', 'dependencies', 'dependencyInsight',
'components', 'model', 'projects', 'properties']
'components', 'model', 'projects', 'properties', 'fixGradleLint']

@Override
void apply(Project project) {
LintRuleRegistry.classLoader = getClass().classLoader
def lintExt = project.extensions.create('gradleLint', GradleLintExtension)

project.tasks.create('fixGradleLint', GradleLintCorrectionTask)
def lint = project.tasks.create('gradleLint', GradleLintTask)
configureReportTask(project, lintExt)
// TODO
// 1. Make gradleLint and fixGradleLint on root run against all subprojects
// 2. Only automatically add root project's gradle lint the end of the build

if(project.rootProject == project) {
project.tasks.create('fixGradleLint', GradleLintCorrectionTask)
project.tasks.create('gradleLint', GradleLintTask)
project.rootProject.apply plugin: GradleLintPlugin
} else {
project.tasks.create('gradleLint') // this task does nothing
project.tasks.create('fixGradleLint').finalizedBy project.rootProject.tasks.getByName('fixGradleLint')
}

project.rootProject.apply plugin: GradleLintPlugin
def rootLint = project.rootProject.tasks.getByName('gradleLint')
configureReportTask(project, lintExt)

// ensure that lint runs
project.tasks.whenTaskAdded { task ->
if(task != lint && !exemptTasks.contains(task.name)) {
task.finalizedBy lint
lint.shouldRunAfter task

// when running a lint-eligible task on a subproject, we want to lint the root project as well
def rootLint = project.rootProject.tasks.getByName('gradleLint')
if (task != rootLint && !exemptTasks.contains(task.name)) {
// when running a lint-eligible task on a subproject, we want to lint the whole project
task.finalizedBy rootLint
rootLint.shouldRunAfter task
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,10 @@ class GradleLintReportTask extends DefaultTask implements VerificationTask, Repo

@TaskAction
void generateReport() {
if(!project.buildFile.exists()) {
return
}

if(reports.enabled) {
def lintExt = project.extensions.getByType(GradleLintExtension)
def registry = new LintRuleRegistry(project)
def ruleSet = RuleSetFactory.configureRuleSet(lintExt.rules.collect { registry.buildRules(it) }.flatten() as List<Rule>)
def registry = new LintRuleRegistry()
def ruleSet = RuleSetFactory.configureRuleSet(lintExt.rules.collect { registry.buildRules(it, project) }.flatten() as List<Rule>)
def results = new FilesystemSourceAnalyzer(baseDirectory: project.projectDir.absolutePath,
includes: project.buildFile.absolutePath).analyze(ruleSet)

Expand Down
Loading

0 comments on commit f07d95b

Please sign in to comment.