Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new dependency declarations even if their transitive source can't be found #469

Merged
merged 2 commits into from
Mar 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,104 @@

package modulecheck.api.finding

import kotlinx.coroutines.runBlocking
import modulecheck.parsing.gradle.Declaration
import modulecheck.parsing.gradle.DependencyDeclaration
import modulecheck.parsing.gradle.ModuleDependencyDeclaration
import modulecheck.parsing.gradle.ProjectPath
import modulecheck.project.ConfiguredDependency
import modulecheck.project.ConfiguredProjectDependency
import modulecheck.project.McProject
import modulecheck.utils.isGreaterThan
import modulecheck.utils.remove
import modulecheck.utils.replaceDestructured
import modulecheck.utils.sortedWith
import org.jetbrains.kotlin.util.prefixIfNot
import org.jetbrains.kotlin.util.suffixIfNot

fun McProject.addDependency(
cpd: ConfiguredProjectDependency,
newDeclaration: DependencyDeclaration,
markerDeclaration: DependencyDeclaration
existingDeclaration: DependencyDeclaration? = null
) {

if (existingDeclaration != null) {
prependStatement(newDeclaration = newDeclaration, existingDeclaration = existingDeclaration)
} else {
addStatement(newDeclaration = newDeclaration)
}

projectDependencies.add(cpd)
}

/**
* Finds the existing project dependency declaration (if there are any) which is the closest match
* to the desired new dependency.
*
* @param matchPathFirst If true, matching project paths will be prioritized over matching
* configurations. If false, configuration matches will take priority over a matching project path.
* @return the closest matching declaration, or null if there are no declarations at all.
*/
suspend fun McProject.closestDeclarationOrNull(
newDependency: ConfiguredProjectDependency,
matchPathFirst: Boolean
): DependencyDeclaration? {

return buildFileParser.dependenciesBlocks()
.firstNotNullOfOrNull { dependenciesBlock ->

val allModuleDeclarations = dependenciesBlock.settings

allModuleDeclarations
.sortedWith(
{
if (matchPathFirst) it.configName == newDependency.configurationName
else it.configName != newDependency.configurationName
},
{ it !is ModuleDependencyDeclaration },
{ (it as? ModuleDependencyDeclaration)?.projectPath?.value ?: "" }
)
.let { sorted ->

sorted.firstOrNull { it.projectPathOrNull() == newDependency.path }
?: sorted.firstOrNull {
it.projectPathOrNull()?.isGreaterThan(newDependency.path) ?: false
}
?: sorted.lastOrNull()
}
?.let { declaration ->

val sameProject = declaration.projectPathOrNull() == newDependency.path

if (sameProject) {
declaration
} else {

val precedingWhitespace = "^\\s*".toRegex()
.find(declaration.statementWithSurroundingText)?.value ?: ""

(declaration as? ModuleDependencyDeclaration)?.copy(
statementWithSurroundingText = declaration.declarationText
.prefixIfNot(precedingWhitespace)
// strip out any config block
.remove(""" *\{[\s\S]*}""".toRegex()),
suppressed = emptyList()
)
}
}
}
}

private fun DependencyDeclaration.projectPathOrNull(): ProjectPath? {
return (this as? ModuleDependencyDeclaration)?.projectPath
}

private fun McProject.prependStatement(
newDeclaration: DependencyDeclaration,
existingDeclaration: DependencyDeclaration
) = synchronized(buildFile) {

val oldStatement = markerDeclaration.statementWithSurroundingText
val oldStatement = existingDeclaration.statementWithSurroundingText
val newStatement = newDeclaration.statementWithSurroundingText

// the `prefixIfNot("\n")` here is important.
Expand All @@ -40,8 +124,39 @@ fun McProject.addDependency(
val buildFileText = buildFile.readText()

buildFile.writeText(buildFileText.replace(oldStatement, combinedStatement))
}

projectDependencies.add(cpd)
private fun McProject.addStatement(
newDeclaration: DependencyDeclaration
) = synchronized(buildFile) {

val newStatement = newDeclaration.statementWithSurroundingText

val buildFileText = buildFile.readText()

runBlocking {
val oldBlockOrNull = buildFileParser.dependenciesBlocks().lastOrNull()

if (oldBlockOrNull != null) {

val newBlock = oldBlockOrNull.fullText
.replaceDestructured("""([\s\S]*)}(\s*)""".toRegex()) { group1, group2 ->

val prefix = group1.trim(' ')
.suffixIfNot("\n")

"$prefix$newStatement}$group2"
}

buildFile.writeText(buildFileText.replace(oldBlockOrNull.fullText, newBlock))
} else {

val newBlock = "\n\ndependencies {\n${newStatement.suffixIfNot("\n")}}"
val newText = buildFileText + newBlock

buildFile.writeText(newText)
}
}
}

fun McProject.removeDependencyWithComment(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ package modulecheck.core
import modulecheck.api.finding.AddsDependency
import modulecheck.api.finding.Finding.Position
import modulecheck.api.finding.addDependency
import modulecheck.api.finding.closestDeclarationOrNull
import modulecheck.core.internal.positionIn
import modulecheck.core.internal.statementOrNullIn
import modulecheck.parsing.gradle.Declaration
import modulecheck.parsing.gradle.ModuleDependencyDeclaration
import modulecheck.parsing.gradle.createProjectDependencyDeclaration
import modulecheck.project.ConfiguredProjectDependency
import modulecheck.project.McProject
import modulecheck.utils.LazyDeferred
Expand Down Expand Up @@ -60,15 +62,24 @@ data class InheritedDependencyFinding(

override suspend fun fix(): Boolean {

val oldDeclaration = declarationOrNull.await() as? ModuleDependencyDeclaration ?: return false
val token = dependentProject
.closestDeclarationOrNull(
newDependency,
matchPathFirst = false
) as? ModuleDependencyDeclaration

val newDeclaration = oldDeclaration.replace(
val newDeclaration = token?.replace(
newConfigName = newDependency.configurationName,
newModulePath = newDependency.path,
testFixtures = newDependency.isTestFixture
)
?: dependentProject.createProjectDependencyDeclaration(
configurationName = newDependency.configurationName,
projectPath = newDependency.path,
isTestFixtures = newDependency.isTestFixture
)

dependentProject.addDependency(newDependency, newDeclaration, oldDeclaration)
dependentProject.addDependency(newDependency, newDeclaration, token)

return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import modulecheck.api.finding.AddsDependency
import modulecheck.api.finding.ModifiesDependency
import modulecheck.api.finding.RemovesDependency
import modulecheck.api.finding.addDependency
import modulecheck.api.finding.closestDeclarationOrNull
import modulecheck.api.finding.removeDependencyWithDelete
import modulecheck.core.internal.statementOrNullIn
import modulecheck.parsing.gradle.ConfigurationName
import modulecheck.parsing.gradle.Declaration
import modulecheck.parsing.gradle.ModuleDependencyDeclaration
import modulecheck.parsing.gradle.createProjectDependencyDeclaration
import modulecheck.project.ConfiguredProjectDependency
import modulecheck.project.McProject
import modulecheck.utils.LazyDeferred
Expand Down Expand Up @@ -64,15 +66,30 @@ data class MustBeApiFinding(

override suspend fun fix(): Boolean {

val oldDeclaration = declarationOrNull.await() as? ModuleDependencyDeclaration ?: return false
val token = dependentProject
.closestDeclarationOrNull(
newDependency,
matchPathFirst = true
) as? ModuleDependencyDeclaration

val newDeclaration = oldDeclaration.replace(
val oldDeclaration = declarationOrNull.await()

val newDeclaration = token?.replace(
newConfigName = newDependency.configurationName,
newModulePath = newDependency.path,
testFixtures = newDependency.isTestFixture
)
?: dependentProject.createProjectDependencyDeclaration(
configurationName = newDependency.configurationName,
projectPath = newDependency.path,
isTestFixtures = newDependency.isTestFixture
)

dependentProject.addDependency(newDependency, newDeclaration, token)

dependentProject.addDependency(newDependency, newDeclaration, oldDeclaration)
dependentProject.removeDependencyWithDelete(oldDeclaration, oldDependency)
if (oldDeclaration != null) {
dependentProject.removeDependencyWithDelete(oldDeclaration, oldDependency)
}

return true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ package modulecheck.core
import modulecheck.api.finding.AddsDependency
import modulecheck.api.finding.ModifiesDependency
import modulecheck.api.finding.RemovesDependency
import modulecheck.api.finding.addDependency
import modulecheck.api.finding.closestDeclarationOrNull
import modulecheck.parsing.gradle.ConfigurationName
import modulecheck.parsing.gradle.DependenciesBlock
import modulecheck.parsing.gradle.ModuleDependencyDeclaration
import modulecheck.parsing.gradle.createProjectDependencyDeclaration
import modulecheck.project.ConfiguredProjectDependency
import modulecheck.project.McProject
import org.jetbrains.kotlin.util.prefixIfNot

data class OverShotDependencyFinding(
override val dependentProject: McProject,
Expand All @@ -45,59 +46,28 @@ data class OverShotDependencyFinding(

override suspend fun fix(): Boolean {

val blocks = dependentProject.buildFileParser
.dependenciesBlocks()
val token = dependentProject
.closestDeclarationOrNull(
newDependency,
matchPathFirst = false
) as? ModuleDependencyDeclaration

val sourceDeclaration = blocks.firstNotNullOfOrNull { block ->

block.getOrEmpty(dependencyProject.path, oldDependency.configurationName)
.firstOrNull()
} ?: return false

val positionBlockDeclarationPair = blocks.firstNotNullOfOrNull { block ->

val match = matchingDeclaration(block) ?: return@firstNotNullOfOrNull null

block to match
} ?: return false

val (block, positionDeclaration) = positionBlockDeclarationPair

val newDeclaration = sourceDeclaration.replace(
configurationName, testFixtures = newDependency.isTestFixture
val newDeclaration = token?.replace(
newConfigName = newDependency.configurationName,
newModulePath = newDependency.path,
testFixtures = newDependency.isTestFixture
)
?: dependentProject.createProjectDependencyDeclaration(
configurationName = newDependency.configurationName,
projectPath = newDependency.path,
isTestFixtures = newDependency.isTestFixture
)

val oldStatement = positionDeclaration.statementWithSurroundingText
val newStatement = oldStatement.plus(
newDeclaration.statementWithSurroundingText
.prefixIfNot("\n")
)

val newBlock = block.lambdaContent.replaceFirst(
oldValue = oldStatement,
newValue = newStatement
)

val fileText = buildFile.readText()
.replace(block.lambdaContent, newBlock)

buildFile.writeText(fileText)

// dependencyProject.removeDependencyWithDelete(oldDependency)
// dependencyProject.addDependency(newDependency)
dependentProject.addDependency(newDependency, newDeclaration, token)

return true
}

private fun matchingDeclaration(block: DependenciesBlock) = block.settings
.filterIsInstance<ModuleDependencyDeclaration>()
.maxByOrNull { declaration -> declaration.configName == configurationName }
?: block.settings
.filterNot { it is ModuleDependencyDeclaration }
.maxByOrNull { declaration -> declaration.configName == configurationName }
?: block.settings
.lastOrNull()

override fun fromStringOrEmpty(): String = ""

override fun toString(): String {
Expand Down
Loading