-
Notifications
You must be signed in to change notification settings - Fork 1k
Assume an implied caret when a semver doesn't have an explicit constraint #631
Conversation
I've thought about various ways of doing this, because I am a bit worried that this whole thing will backfire on us. And I've considered something like this. I think it might work well for people who understand the system, but it would be substantially worse for people who don't - and newbies are the ones we need to optimize for, here. Implicit caret is going to be surprising enough, but if the system writes it one way (with caret), then users choose to write a constraint without a caret then get the same behavior, that'll just be tremendously confusing, because the user is likely to understand their choice relative to the way the system does it. Especially given the confusion people often have with the unary compatibility operators ( |
internal/gps/solve_basic_test.go
Outdated
@@ -486,11 +486,11 @@ var basicFixtures = map[string]basicFixture{ | |||
}, | |||
"simple dependency tree": { | |||
ds: []depspec{ | |||
mkDepspec("root 0.0.0", "a 1.0.0", "b 1.0.0"), |
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.
omg this is awful and never even occurred to me i can't believe you did all this i hope you wrote a script you're amazing
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.
No worries, it wasn't hard to add, or to remove!
internal/gps/constraints.go
Outdated
@@ -52,7 +52,7 @@ type Constraint interface { | |||
// If the input string cannot be made into a valid semver Constraint, an error | |||
// is returned. | |||
func NewSemverConstraint(body string) (Constraint, error) { |
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.
I think we actually might want to mirror the change in Masterminds/semver
instead, and introduce a func NewSemverConstraintIC()
. That way we still give the implementing tool the choice as to whether they're using implicit carets or not; gps can remain agnostic.
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.
If I go that route, I can undo the changes to gps testdata, and have gps test without implied carets. So that's a win allaround! ❤️
I typed up a lively response about user expectations, reading documentation, and 🥕s, and then realized that the primary goal may not actually be having the least surprising user experience, but instead maximizing the likelihood that libraries use looser constraints, i.e. "the greater good". If we don't expect people to bother reading the docs, then we can take advantage of this behavior so that more people are using carets whether they realize or not. So on that front, I'm convinced. 😉 |
This is absolutely 💯 the goal. We want the non-knowledgeable user's default choice to be one with less likelihood of harming the ecosystem.
And this is definitely why I'm worried about it backfiring 😄 On that front, I'm just glad that we're not the first to try this. |
Okeedokee!
|
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.
tiiiiiiny little docs nits 😄
@@ -64,6 +72,24 @@ func NewSemverConstraint(body string) (Constraint, error) { | |||
return semverConstraint{c: c}, nil | |||
} | |||
|
|||
// NewSemverConstraintIC attempts to construct a semver Constraint object from the | |||
// input string, defaulting to a caret, ^, when no constraint is specified. |
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.
nit: s/constraint/operator/
// than =. | ||
// | ||
// In the same way that String() is the inverse of NewConstraint(), this | ||
// method is the inverse of to NewSemverConstraintIC(). |
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.
nit: "of to"
// than =. | ||
// | ||
// In the same way that String() is the inverse of NewConstraint(), this | ||
// method is the inverse of to NewSemverConstraintIC(). |
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.
nit: "of to"
Actually, don't worry about those nits - I'm going to merge this into the running branch, and I'll just fix em there 😄 |
(hah those are typos i made in the upstream!) |
Fixes #225
When reading in a manifest, and no constraint has been applied to a version, assume the caret
^
constraint by default. Now a user must add the=
prefix to restrict a version to exactly the version specified and maintain the previous behavior.I decided to accept implied carets but never write out implied carets, i.e. be liberal in what we accept and strict in what we output. So wheninit
generates a manifest, it continues to output carets asdep
does today. I prefer this over outputting bare versions because it is more clear to users; it should cut down on issues where someone is surprised that the version wasn't "pinned". I left comments in as to why we aren't using ImpliedCaretString.Existing tests which relied upon a bare version being interpreted with an
=
constraint have been updated with explicit=
prefixes.