Permalink
Browse files

Add caret operator

The caret operator has rough semantics "compatible with X". Specifically, it allows any version that is *at least* the specified version, but *less than* the next *major* version.

In other words, `^1.2.3` is equivalent to `>=1.2.3-0 <2.0.0`.
  • Loading branch information...
1 parent f959a58 commit 73b8fa4047f713e7a3e1c08a07139fea42c85a68 @agnoster agnoster committed with isaacs Jul 21, 2013
Showing with 77 additions and 1 deletion.
  1. +7 −0 README.md
  2. +59 −0 semver.js
  3. +11 −1 test/index.js
View
7 README.md
@@ -60,9 +60,16 @@ The following range styles are supported:
using tilde operators, prerelease versions are supported as well,
but a prerelease of the next significant digit will NOT be
satisfactory, so `1.3.0-beta` will not satisfy `~1.2.3`.
+* `^1.2.3` := `>=1.2.3-0 <2.0.0-0` "Compatible with 1.2.3". When
+ using caret operators, anything from the specified version (including
+ prerelease) will be supported up to, but not including, the next
+ major version (or its prereleases). `1.5.1` will satisfy `^1.2.3`,
+ while `1.2.2` and `2.0.0-beta` will not.
* `~1.2` := `>=1.2.0-0 <1.3.0-0` "Any version starting with 1.2"
+* `^1.2` := `>=1.2.0-0 <2.0.0-0` "Any version compatible with 1.2"
* `1.2.x` := `>=1.2.0-0 <1.3.0-0` "Any version starting with 1.2"
* `~1` := `>=1.0.0-0 <2.0.0-0` "Any version starting with 1"
+* `^1` := `>=1.0.0-0 <2.0.0-0` "Any version compatible with 1"
* `1.x` := `>=1.0.0-0 <2.0.0-0` "Any version starting with 1"
View
59 semver.js
@@ -169,6 +169,19 @@ src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$';
var TILDELOOSE = R++;
src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$';
+// Caret ranges.
+// Meaning is "at least and backwards compatible with"
+var LONECARET = R++;
+src[LONECARET] = '(?:\\^)';
+
+var CARETTRIM = R++;
+src[CARETTRIM] = src[LONECARET] + '\s+';
+var caretTrimReplace = '$1';
+
+var CARET = R++;
+src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$';
+var CARETLOOSE = R++;
+src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$';
// A simple gt/lt/eq thing, or just "" to indicate "any version"
var COMPARATORLOOSE = R++;
@@ -602,6 +615,9 @@ Range.prototype.parseRange = function(range) {
// `~ 1.2.3` => `~1.2.3`
range = range.replace(re[TILDETRIM], tildeTrimReplace);
+ // `^ 1.2.3` => `^1.2.3`
+ range = range.replace(re[CARETTRIM], caretTrimReplace);
+
// normalize spaces
range = range.split(/\s+/).join(' ');
@@ -640,6 +656,8 @@ function toComparators(range, loose) {
// turn into a set of JUST comparators.
function parseComparator(comp, loose) {
debug('comp', comp);
+ comp = replaceCarets(comp, loose);
+ debug('caret', comp);
comp = replaceTildes(comp, loose);
debug('tildes', comp);
comp = replaceXRanges(comp, loose);
@@ -694,6 +712,47 @@ function replaceTilde(comp, loose) {
});
}
+// ^ --> * (any, kinda silly)
+// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0
+// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0
+// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0
+// ^1.2.3 --> >=1.2.3 <2.0.0
+// ^1.2.0 --> >=1.2.0 <2.0.0
+function replaceCarets(comp, loose) {
+ return comp.trim().split(/\s+/).map(function(comp) {
+ return replaceCaret(comp, loose);
+ }).join(' ');
+}
+
+function replaceCaret(comp, loose) {
+ var r = loose ? re[CARETLOOSE] : re[CARET];
+ return comp.replace(r, function(_, M, m, p, pr) {
+ debug('caret', comp, _, M, m, p, pr);
+ var ret;
+
+ if (isX(M))
+ ret = '';
+ else if (isX(m))
+ ret = '>=' + M + '.0.0-0 <' + (+M + 1) + '.0.0-0';
+ else if (isX(p))
+ // ~1.2 == >=1.2.0- <1.3.0-
+ ret = '>=' + M + '.' + m + '.0-0 <' + (+M + 1) + '.0.0-0';
+ else if (pr) {
+ debug('replaceCaret pr', pr);
+ if (pr.charAt(0) !== '-')
+ pr = '-' + pr;
+ ret = '>=' + M + '.' + m + '.' + p + pr +
+ ' <' + (+M + 1) + '.0.0-0';
+ } else
+ // ~1.2.3 == >=1.2.3-0 <1.3.0-0
+ ret = '>=' + M + '.' + m + '.' + p + '-0' +
+ ' <' + (+M + 1) + '.0.0-0';
+
+ debug('caret return', ret);
+ return ret;
+ });
+}
+
function replaceXRanges(comp, loose) {
debug('replaceXRanges', comp, loose);
return comp.split(/\s+/).map(function(comp) {
View
12 test/index.js
@@ -207,7 +207,10 @@ test('\nrange tests', function(t) {
['>=1.2.1 >=1.2.3', '1.2.3'],
['<=1.2.3', '1.2.3-beta'],
['>1.2', '1.3.0-beta'],
- ['>=1.2', '1.2.8']
+ ['>=1.2', '1.2.8'],
+ ['^1.2.3', '1.8.1'],
+ ['^1.2.3', '1.2.3-beta'],
+ ['^1.2', '1.4.2']
].forEach(function(v) {
var range = v[0];
var ver = v[1];
@@ -268,6 +271,9 @@ test('\nnegative range tests', function(t) {
['<1.2.3', '1.2.3-beta'],
['=1.2.3', '1.2.3-beta'],
['>1.2', '1.2.8'],
+ ['^1.2.3', '2.0.0-alpha'],
+ ['^1.2.3', '1.2.2'],
+ ['^1.2', '1.1.9'],
// invalid ranges never satisfied!
['blerg', '1.2.3'],
['git+https://user:password0123@github.com/foo', '123.0.0', true]
@@ -374,6 +380,10 @@ test('\nvalid range test', function(t) {
['~> 1', '>=1.0.0-0 <2.0.0-0'],
['~1.0', '>=1.0.0-0 <1.1.0-0'],
['~ 1.0', '>=1.0.0-0 <1.1.0-0'],
+ ['^1.2.3', '>=1.2.3-0 <2.0.0-0'],
+ ['^ 1', '>=1.0.0-0 <2.0.0-0'],
+ ['^2.3', '>=2.3.0-0 <3.0.0-0'],
+ ['^1.2.3-beta.4', '>=1.2.3-beta.4 <2.0.0-0'],
['<1', '<1.0.0-0'],
['< 1', '<1.0.0-0'],
['>=1', '>=1.0.0-0'],

0 comments on commit 73b8fa4

Please sign in to comment.