-
Notifications
You must be signed in to change notification settings - Fork 502
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
Refactor UpdateInfoUrlFinder
, enhance ForgeType
#3145
Refactor UpdateInfoUrlFinder
, enhance ForgeType
#3145
Conversation
This change replaces several switch statements in `UpdateInfoUrlFinder` by adding a couple of additional methods to `ForgeType`. Overall, I feel the code under `possibleUpdateInfoUrls()` is much simplified, and several methods now have one fewer parameter (`forgeUrl`). https://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html https://refactoring.guru/replace-conditional-with-polymorphism The thing I especially like about this pattern is that as new types of forge are implemented, the responsiblites for supporting that new `ForgeType` are all in one place, rather than spread across several methods elsewhere in the codebase. `ForgeType` gains two new fields of type `FileUriPattern` & `DiffUriPattern`, which are responsible for understanding the url structure of files & diffs in that forge type. `FileUriPattern` & `DiffUriPattern` have been designed so that they can be concisely implemented using the lambda syntax for Single Abstract Method (SAM) types: https://www.scala-lang.org/news/2.12.0/#lambda-syntax-for-sam-types They also can be easily copied as values and used by different forge types that have the same url structure (eg GitLab has exactly the same url structure as GitHub, whereas Gitea has the same url structure for diffs, but not files). Meaning of `ForgeType` parameter changes on some methods ======================================================== Note that with this change, the semantics of the `ForgeType` parameter passed to the `possibleVersionDiffs()` & `possibleUpdateInfoUrls()` methods changes: * OLD meaning : "assume the repo has this forge type, if the repo's host matchs the configured host, otherwise work out the forge type from the repos host". * NEW meaning : "this is the determined forge type of the repo" - already determined as described by the logic described above The method for working out the `ForgeType` of a repo has also moved from `UpdateInfoUrlFinder.forgeTypeFromRepoUrl()` to `ForgeType.fromRepoUrl()` - and it's now called only once, not twice. Deleted tests ------------- Two test cases in `UpdateInfoUrlFinderTest` are deleted because they are testing that `possibleVersionDiffs()` & `possibleUpdateInfoUrls()` would return an empty list if called with a repo url that doesn't have an identifiable `ForgeType` - those methods would now not get called at all, so the tests don't make sense, and can be removed. Azure 'diff' URLs bugfix ======================== The format of Azure 'diff' urls is slightly different to what Scala Steward has been generating up to now - the tag names used in actual Azure urls are prefixed with `GT` on the query parameter values: https://dev.azure.com/rtyley/scala-steward-testing/_git/scala-steward-testing/branchCompare?baseVersion=GTv1.0.0&targetVersion=GTv1.0.1 Removing the `GT` prefixes will give an error: https://dev.azure.com/rtyley/scala-steward-testing/_git/scala-steward-testing/branchCompare?baseVersion=v1.0.0&targetVersion=v1.0.1
`forgeType` & `repoUrl` are two parameters that are passed together in several method calls, suggesting that they belong together in a single parameter object - a `ForgeRepo` where the type of forge indicated by the repoUrl has been established:: https://refactoring.com/catalog/introduceParameterObject.html https://refactoring.guru/introduce-parameter-object https://docondev.com/blog/2020/6/2/refactoring-introduce-parameter-object Those two pieces of information - `forgeType` & `repoUrl` - are all that are required to generate urls for diffs or file display, meaning that actually `ForgeRepo` can be more than a simple parameter object, and provide convenient methods to generate those urls. New tests for generated file & diff urls ======================================== This change also adds `ForgeRepoTest`, which gives clear examples of what diff and file urls should be for different forge types. The urls included in the test correspond to real live repos as much as possible, so it's possible to click through on them and see that they do indeed correspond to real live urls genuinely served by those different forge hosts.
The `currentVersion` & `nextVersion` parameters are paired together relatively often, and because they are of the same type (`Version`) they are susceptible to being accidentally swapped over without any compiler error being raised. Encapsulating them into a single concept (that of a `Version.Update`) makes code a little clearer, and reduces the number of individual parameters being passed on method calls.
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #3145 +/- ##
==========================================
+ Coverage 91.03% 91.07% +0.03%
==========================================
Files 162 163 +1
Lines 3404 3406 +2
Branches 321 307 -14
==========================================
+ Hits 3099 3102 +3
+ Misses 305 304 -1
☔ View full report in Codecov by Sentry. |
trait DiffUriPattern { def forDiff(from: String, to: String): Uri => Uri } | ||
trait FileUriPattern { def forFile(fileName: String): Uri => Uri } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FileUriPattern
& DiffUriPattern
have been designed so that they can be concisely implemented using the lambda syntax for Single Abstract Method (SAM) types, eg:
scala-steward/modules/core/src/main/scala/org/scalasteward/core/forge/ForgeType.scala
Lines 88 to 89 in 13380eb
val diffs: DiffUriPattern = (from, to) => _ / "compare" / s"$from...$to" | |
val files: FileUriPattern = fileName => _ / "blob" / "master" / fileName |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s"${onPremForgeUrl}foo/bar/branchCompare?baseVersion=GTv$v1&targetVersion=GTv$v2", | ||
s"${onPremForgeUrl}foo/bar/branchCompare?baseVersion=GT$v1&targetVersion=GT$v2", | ||
s"${onPremForgeUrl}foo/bar/branchCompare?baseVersion=GTrelease-$v1&targetVersion=GTrelease-$v2" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the format of Azure 'diff' urls is slightly different to what Scala Steward has been generating up to now - the tag names used in actual Azure urls are prefixed with GT
on the query parameter values:
Removing the GT
prefixes will get a Unrecognized VersionSpec format
error from Azure:
assertEquals( | ||
possibleVersionDiffs( | ||
GitHub, | ||
onPremForgeUrl, | ||
uri"https://scalacenter.github.io/scalafix/", | ||
v1, | ||
v2 | ||
), | ||
List.empty | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assertion is deleted because it was testing that possibleVersionDiffs()
would return an empty list if called with a repo url that doesn't have an identifiable ForgeType
- possibleVersionDiffs()
is now only ever called if a ForgeType
has been identified, and that information is part of the required ForgeRepo
parameter on the possibleVersionDiffs()
method.
Consequently this test no longer applies for the new method signature/parameter semantic! A similar assertion was deleted for possibleUpdateInfoUrls()
below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense! Thanks for cleaning that up 😊
test("possibleUpdateInfoUrls: repoUrl is a home page") { | ||
val repoUrl = uri"https://scalacenter.github.io/scalafix/" | ||
val obtained = possibleUpdateInfoUrls(GitHub, uri"https://github.com", repoUrl, v1, v2) | ||
assertEquals(obtained, List.empty) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This testcase is deleted because it was testing that possibleUpdateInfoUrls()
would return an empty list if called with a repo url that doesn't have an identifiable ForgeType
- possibleUpdateInfoUrls()
is now only ever called if a ForgeType
has been identified, and that information is part of the required ForgeRepo
parameter on the possibleUpdateInfoUrls()
method.
Consequently this test no longer applies for the new method signature/parameter semantic! A similar assertion was deleted for possibleVersionDiffs()
above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you very much for this @rtyley. This was one of the best PRs I've ever encounter. Such a pleasure to review!
import org.scalasteward.core.forge.ForgeType._ | ||
import org.scalasteward.core.util.unexpectedString | ||
|
||
sealed trait ForgeType extends Product with Serializable { | ||
def publicWebHost: Option[String] | ||
val diffs: DiffUriPattern |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
quibble: Can we add some ScalaDocs here to explain what this method should do?
This comment follows the conventionalcomments.org standard
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, done with 905250c !
trait DiffUriPattern { def forDiff(from: String, to: String): Uri => Uri } | ||
trait FileUriPattern { def forFile(fileName: String): Uri => Uri } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ / "branchCompare" withQueryParams Map( | ||
"baseVersion" -> s"GT$from", | ||
"targetVersion" -> s"GT$to" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: You can also use the operator to add query params +?
_ / "branchCompare" +? ("baseVersion", s"GT$from") +? ("targetVersion", s"GT$to")
This comment follows the conventionalcomments.org standard
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh, nice one! Updated with fca39d4 👍
assertEquals( | ||
possibleVersionDiffs( | ||
GitHub, | ||
onPremForgeUrl, | ||
uri"https://scalacenter.github.io/scalafix/", | ||
v1, | ||
v2 | ||
), | ||
List.empty | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense! Thanks for cleaning that up 😊
* forge, etc, then we can know how to construct many of the urls for common resources existing at | ||
* that repo host- for instance, the url to view a particular file, or to diff two commits. | ||
*/ | ||
case class ForgeRepo(forgeType: ForgeType, repoUrl: Uri) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: 👏🏼 👏🏼 👏🏼
This comment follows the conventionalcomments.org standard
/** As much as possible, uris in this test suite should aim to be real, clickable, uris that | ||
* actually go to real pages, allowing developers working against this test suite to verify that | ||
* the url patterns constructed here actually _are_ valid, and match what forges are currently | ||
* using in the real world. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: This is gold!
This comment follows the conventionalcomments.org standard
@@ -124,6 +124,8 @@ final case class Version(value: String) { | |||
} | |||
|
|||
object Version { | |||
case class Update(currentVersion: Version, nextVersion: Version) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
praise: 👏🏼 👏🏼
This comment follows the conventionalcomments.org standard
Adding scaladoc as suggested by @alejandrohdezma in review: scala-steward-org#3145 (comment) See also: scala-steward-org#3145 (comment)
Ah what lovely feedback! Thank you! ✨ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for addressing those so quickly! Amazing contribution!
This is another example of replacing conditionals with polymorphism on `ForgeType`. This was a theme in commit b42866b, merged in this PR back in August 2023: scala-steward-org#3145 A benefit of this pattern is as new types of forge are implemented, the responsibilities for supporting that new `ForgeType` are all in one place, rather than spread across several methods elsewhere in the codebase. To confess, I think there's probably a further refactor that could be done, to introduce a better parameter object denoting a 'Pull Request Head' (that conceivably could better model the way that some `ForgeType`s don't even support forking) - but that can wait for another day, fixing scala-steward-org#3300 is more urgent!
This is another example of replacing conditionals with polymorphism on `ForgeType`. This was a theme in commit b42866b, merged in this PR back in August 2023: scala-steward-org#3145 A benefit of this pattern is as new types of forge are implemented, the responsibilities for supporting that new `ForgeType` are all in one place, rather than spread across several methods elsewhere in the codebase. To confess, I think there's probably a further refactor that could be done, to introduce a better parameter object denoting a 'Pull Request Head' (that conceivably could better model the way that some `ForgeType`s don't even support forking) - but that can wait for another day, fixing scala-steward-org#3300 is more urgent!
This is another example of replacing conditionals with polymorphism on `ForgeType`. This was a theme in commit b42866b, merged in this PR back in August 2023: #3145 A benefit of this pattern is as new types of forge are implemented, the responsibilities for supporting that new `ForgeType` are all in one place, rather than spread across several methods elsewhere in the codebase. To confess, I think there's probably a further refactor that could be done, to introduce a better parameter object denoting a 'Pull Request Head' (that conceivably could better model the way that some `ForgeType`s don't even support forking) - but that can wait for another day, fixing #3300 is more urgent!
This PR has 3 commits, which each can be reviewed separately and contain a single refactor:
UpdateInfoUrlFinder
with polymorphism onForgeType
- responsibility for generating urls displaying diffs & files moves toForgeType
ForgeRepo
as a parameter objectVersion.Update
as a parameter object(as it happens, each subsequent refactor here is smaller than the last)
Cumulatively, the benefits of these changes are:
ForgeType
, without also having to search out additional switch statements elsewhere in the codebasepossibleUpdateInfoUrls()
&possibleVersionDiffs()
both go from 5 to 2 parameters.ForgeRepoTest
, has been introduced which gives clear examples of what diff and file urls look like for different forge types. The urls included in the tests correspond to real live repos as much as possible, so it's possible to click through on them and see that they do indeed correspond to real urls genuinely served by those different forge hosts (note that Azure diff urls have been fixed). The tests here have some overlap withUpdateInfoUrlFinderTest
, but are more focussed on making sure that generated url structure is correct, rather than ensuring the correct info sources (release notes, diffs) have been identified for a given dependency update.