Skip to content

Commit

Permalink
Added PythonFunctionCallHeuristic (#148)
Browse files Browse the repository at this point in the history
* Added PythonMethodHeuristic

Try in PythonMethodHeuristic Logic

Fixed PythonMethodHeuristic

* Lint fix

* Fixes after review

* Fixes after review

* Fixes after review

* Fixes after review

* Fixes after review

* Fixes after review
  • Loading branch information
mikkoziel authored Feb 22, 2023
1 parent 1663fdd commit 0c0110b
Show file tree
Hide file tree
Showing 13 changed files with 193 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.virtuslab.bazelsteward.core.common.TextFile
import org.virtuslab.bazelsteward.core.common.UpdateSuggestion
import org.virtuslab.bazelsteward.core.library.SemanticVersion
import org.virtuslab.bazelsteward.core.replacement.LibraryUpdateResolver
import org.virtuslab.bazelsteward.core.replacement.PythonFunctionCallHeuristic
import org.virtuslab.bazelsteward.core.replacement.VersionOnlyHeuristic
import org.virtuslab.bazelsteward.core.replacement.VersionReplacementHeuristic
import org.virtuslab.bazelsteward.core.replacement.WholeLibraryHeuristic
Expand All @@ -22,6 +23,11 @@ class VersionReplacementHeuristicTest {
val correctPositionFor235: Int = 2401
val correctPositionFor120: Int = 2263
val correctPositionFor160: Int = 2464
val correctPositionFor3200jre: Int = 2764
val correctPositionFor113: Int = 2935
val correctPositionFor3212: Int = 3128
val correctPositionFor4132: Int = 3058
val correctPositionFor852: Int = 3326

@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
Expand Down Expand Up @@ -188,6 +194,71 @@ class VersionReplacementHeuristicTest {
}
}

@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class PythonFunctionCallHeuristicTest {

@Test
fun `should return correct position offset maven artifact`() {
val library = library("com.google.guava", "guava-testlib", "31.1.0-jre")
val suggestedVersion = version("32.0.0-jre")

val result = resolveUpdates(library, suggestedVersion, PythonFunctionCallHeuristic)

result?.offset shouldBe correctPositionFor3200jre
}

@Test
fun `should return correct position offset maven artifact named parameters`() {
val library = library("com.google.truth", "truth", "1.1.3")
val suggestedVersion = version("1.2.0")

val result = resolveUpdates(library, suggestedVersion, PythonFunctionCallHeuristic)

result?.offset shouldBe correctPositionFor113
}

@Test
fun `should return correct position offset scala dep`() {
val library = library("org.scalactic", "scalactic", "3.2.12")
val suggestedVersion = version("4.0.0")

val result = resolveUpdates(library, suggestedVersion, PythonFunctionCallHeuristic)

result?.offset shouldBe correctPositionFor3212
}

@Test
fun `should return null for wrong scala dep`() {
val library = library("org.scalactic", "scalactic", "3.2.90")
val suggestedVersion = version("4.0.0")

val result = resolveUpdates(library, suggestedVersion, PythonFunctionCallHeuristic)

result shouldBe null
}

@Test
fun `should return correct position offset scala dep named parameters`() {
val library = library("junit", "junit", "4.13.2")
val suggestedVersion = version("4.14.0")

val result = resolveUpdates(library, suggestedVersion, PythonFunctionCallHeuristic)

result?.offset shouldBe correctPositionFor4132
}

@Test
fun `should return correct position offset scala dep with scala version`() {
val library = library("com.sksamuel.elastic4s", "elastic4s-client-akka_2.12", "8.5.2")
val suggestedVersion = version("8.6.0")

val result = resolveUpdates(library, suggestedVersion, PythonFunctionCallHeuristic)

result?.offset shouldBe correctPositionFor852
}
}

@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class CompareHeuristicTest {
Expand Down Expand Up @@ -235,7 +306,7 @@ class VersionReplacementHeuristicTest {

private val resolver = LibraryUpdateResolver()

private val allHeuristics = listOf(WholeLibraryHeuristic, VersionOnlyHeuristic).toTypedArray()
private val allHeuristics = listOf(WholeLibraryHeuristic, VersionOnlyHeuristic, PythonFunctionCallHeuristic).toTypedArray()

private fun resolveUpdates(
library: MavenCoordinates,
Expand Down
20 changes: 20 additions & 0 deletions app/src/test/resources/WORKSPACE.bzlignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,26 @@ maven_install(
"io.get-coursier:interface:1.0.11",
"commons-io:commons-io:2.11.0",
"io.grpc:grpc-core:%s" % GRPC_VERSION,
maven.artifact(
"com.google.guava",
"guava-testlib",
"31.1.0-jre",
testonly = True
),
maven.artifact(
group = "com.google.truth",
artifact = "truth",
version = "1.1.3",
),
_scala_dep(
group = "junit",
artifact = "junit",
version = "4.13.2",
),
_scala_dep("org.scalactic", "scalactic", "3.2.12"),
_java_dep("org.slf4j", "slf4j-api", "1.7.36"),
_java_dep("com.github.luben", "zstd-jni", "1.5.2-5"),
_scala_dep("com.sksamuel.elastic4s", "elastic4s-client-akka", "8.5.2"),
],
fetch_sources = False,
repositories = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class UpdateLogic {

fun maxAvailableVersion(filterVersionComponent: (a: SemanticVersion) -> Boolean): Version? =
availableVersions
.filter { version -> updateRules.pinningStrategy?.test(version) ?: true }
.filter { version -> updateRules.pinningStrategy.test(version) }
.mapNotNull { version -> version.toSemVer(updateRules.versioningSchema)?.let { version to it } }
.filter { it.second.prerelease.isBlank() && filterVersionComponent(it.second) }
.maxByOrNull { it.second }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.virtuslab.bazelsteward.core.library

abstract class LibraryId {
abstract fun associatedStrings(): List<String>
abstract fun associatedStrings(): List<List<String>>
abstract val name: String
final override fun toString(): String = name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.virtuslab.bazelsteward.core.replacement

import org.virtuslab.bazelsteward.core.common.FileChange
import org.virtuslab.bazelsteward.core.common.TextFile
import org.virtuslab.bazelsteward.core.common.UpdateSuggestion

object PythonFunctionCallHeuristic : VersionReplacementHeuristic {
override val name: String = "python-function-call"

override fun apply(files: List<TextFile>, updateSuggestion: UpdateSuggestion): LibraryUpdate? {
val functionCalls = getFunctionCalls(files)

if (functionCalls.isNotEmpty()) {
val associatedStringVariants = updateSuggestion.currentLibrary.id.associatedStrings()
val functionCallsWithAssociatedStrings = functionCalls.filter { call ->
associatedStringVariants.any { strings ->
strings.all { call.matchedText.contains(it) }
}
}

val currentVersion = updateSuggestion.currentLibrary.version.value
val matchedVersion = functionCallsWithAssociatedStrings.firstNotNullOfOrNull { call ->
Regex.fromLiteral(currentVersion).find(call.matchedText)?.let { call.subMatch(it) }
} ?: return null

return LibraryUpdate(
updateSuggestion,
listOf(
FileChange(
matchedVersion.origin,
matchedVersion.offset,
updateSuggestion.currentLibrary.version.value.length,
updateSuggestion.suggestedVersion.value
)
)
)
}
return null
}

private fun getFunctionCalls(files: List<TextFile>): List<MatchedText> {
val pythonMethodRegex = Regex("""\w+\([\w\n\s".\-,=]+\)""")
return files.flatMap { textFile ->
pythonMethodRegex.findAll(textFile.content).map { MatchedText(it, textFile.path) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,20 @@ object VersionOnlyHeuristic : VersionReplacementHeuristic {
override fun apply(files: List<TextFile>, updateSuggestion: UpdateSuggestion): LibraryUpdate? {
val currentVersion = updateSuggestion.currentLibrary.version.value
val regex = Regex(Regex.escape(currentVersion))
val matchResult = files.firstNotNullOfOrNull { regex.find(it.content)?.to(it.path) } ?: return null
matchResult.first.next()?.let { return null }
val versionGroup = matchResult.first.groups[0] ?: return null
val matchResult = files.firstNotNullOfOrNull { f ->
regex.find(f.content)?.let {
MatchedText(it, f.path)
}
} ?: return null
matchResult.match.next()?.let { return null }
val versionOffset = matchResult.offsetLastMatchGroup ?: return null

return LibraryUpdate(
updateSuggestion,
listOf(
FileChange(
matchResult.second,
versionGroup.range.first,
matchResult.origin,
versionOffset,
updateSuggestion.currentLibrary.version.value.length,
updateSuggestion.suggestedVersion.value
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,29 @@ package org.virtuslab.bazelsteward.core.replacement
import org.virtuslab.bazelsteward.core.common.FileChange
import org.virtuslab.bazelsteward.core.common.TextFile
import org.virtuslab.bazelsteward.core.common.UpdateSuggestion
import java.nio.file.Path

data class LibraryUpdate(
val suggestion: UpdateSuggestion,
val fileChanges: List<FileChange>
)

data class MatchedText(
val match: MatchResult,
val origin: Path,
val baseOffset: Int = 0
) {
val offset: Int
get() = match.range.start + baseOffset

val offsetLastMatchGroup: Int?
get() = match.groups.last()?.range?.start
val matchedText: String
get() = match.value

fun subMatch(subMatch: MatchResult): MatchedText = MatchedText(subMatch, origin, baseOffset = offset)
}

interface VersionReplacementHeuristic {
val name: String
fun apply(files: List<TextFile>, updateSuggestion: UpdateSuggestion): LibraryUpdate?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,24 @@ object WholeLibraryHeuristic : VersionReplacementHeuristic {
override fun apply(files: List<TextFile>, updateSuggestion: UpdateSuggestion): LibraryUpdate? {
val markers = updateSuggestion.currentLibrary.id.associatedStrings()
val currentVersion = updateSuggestion.currentLibrary.version.value
val regex =
(markers + currentVersion).map { """(${Regex.escape(it)})""" }.reduce { acc, s -> "$acc.*$s" }.let { Regex(it) }
val matchResult = files.firstNotNullOfOrNull { regex.find(it.content)?.to(it.path) } ?: return null
val versionGroup = matchResult.first.groups[3] ?: return null
val regexes = markers.map { marker ->
(marker + currentVersion).map { """(${Regex.escape(it)})""" }.reduce { acc, s -> "$acc.*$s" }.toRegex()
}
val matchResult = regexes.firstNotNullOfOrNull { regex ->
files.firstNotNullOfOrNull { textFile ->
regex.find(textFile.content)?.let {
MatchedText(it, textFile.path)
}
}
} ?: return null
val versionOffset = matchResult.offsetLastMatchGroup ?: return null

return LibraryUpdate(
updateSuggestion,
listOf(
FileChange(
matchResult.second,
versionGroup.range.first,
matchResult.origin,
versionOffset,
updateSuggestion.currentLibrary.version.value.length,
updateSuggestion.suggestedVersion.value
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class GithubTest {
private fun simpleBranch(branch: String): GitBranch {
val split = branch.split('/', limit = 3)
val lib = object : LibraryId() {
override fun associatedStrings(): List<String> = emptyList()
override fun associatedStrings(): List<List<String>> = emptyList()
override val name = split[1]
}
val ver = SimpleVersion(split[2])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ sealed class RuleLibraryId : LibraryId() {
override val name: String
get() = ruleName

override fun associatedStrings(): List<String> = listOf(downloadUrl, sha256)
override fun associatedStrings(): List<List<String>> = listOf(listOf(downloadUrl, sha256))

data class ReleaseArtifact(
override val sha256: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.virtuslab.bazelsteward.core.library.VersioningSchema
private val logger = KotlinLogging.logger {}

object BazelLibraryId : LibraryId() {
override fun associatedStrings(): List<String> = listOf("", "USE_BAZEL_VERSION")
override fun associatedStrings(): List<List<String>> = listOf(listOf("USE_BAZEL_VERSION"))

override val name: String
get() = "bazel"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import org.virtuslab.bazelsteward.core.library.SimpleVersion
import org.virtuslab.bazelsteward.core.library.Version

data class MavenLibraryId(val group: String, val artifact: String) : LibraryId() {
override fun associatedStrings(): List<String> = listOf(group, artifact)
override fun associatedStrings(): List<List<String>> {
return Regex("""_[\d.]+$""").find(artifact)?.let {
listOf(listOf(group, artifact), listOf(group, artifact.removeRange(it.range)))
} ?: listOf(listOf(group, artifact))
}

override val name: String
get() = "$group:$artifact"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.virtuslab.bazelsteward.core.DependencyKind
import org.virtuslab.bazelsteward.core.PathPattern
import org.virtuslab.bazelsteward.core.library.Library
import org.virtuslab.bazelsteward.core.library.Version
import org.virtuslab.bazelsteward.core.replacement.PythonFunctionCallHeuristic
import org.virtuslab.bazelsteward.core.replacement.VersionOnlyHeuristic
import org.virtuslab.bazelsteward.core.replacement.VersionReplacementHeuristic
import org.virtuslab.bazelsteward.core.replacement.WholeLibraryHeuristic
Expand Down Expand Up @@ -37,6 +38,7 @@ class MavenDependencyKind(

override val defaultVersionReplacementHeuristics: List<VersionReplacementHeuristic> = listOf(
WholeLibraryHeuristic,
VersionOnlyHeuristic
VersionOnlyHeuristic,
PythonFunctionCallHeuristic
)
}

0 comments on commit 0c0110b

Please sign in to comment.