1
1
import test from 'ava' ;
2
+ import semver from 'semver' ;
2
3
import semverRegex from './index.js' ;
3
4
4
- const fixtures = [
5
+ const validStrings = [
5
6
'0.0.0' ,
6
7
'0.10.0' ,
7
8
'v1.0.0' ,
8
9
'0.0.0-foo' ,
10
+ '0.0.0-foo-bar-baz' ,
9
11
'1.2.3-4' ,
10
12
'2.7.2+asdf' ,
11
13
'1.2.3-a.b.c.10.d.5' ,
12
14
'2.7.2-foo+bar' ,
13
15
'1.2.3-alpha.10.beta' ,
14
16
'1.2.3-alpha.10.beta+build.unicorn.rainbow' ,
15
- 'foo 0.0.0 bar 0.0.0' ,
16
- '99999.99999.99999'
17
+ '99999.99999.99999' ,
18
+
19
+ // Pulled from https://regex101.com/r/vkijKf/1/
20
+ '0.0.4' ,
21
+ '1.2.3' ,
22
+ '10.20.30' ,
23
+ '1.1.2-prerelease+meta' ,
24
+ '1.1.2+meta' ,
25
+ '1.1.2+meta-valid' ,
26
+ '1.0.0-alpha' ,
27
+ '1.0.0-beta' ,
28
+ '1.0.0-alpha.beta' ,
29
+ '1.0.0-alpha.beta.1' ,
30
+ '1.0.0-alpha.1' ,
31
+ '1.0.0-alpha0.valid' ,
32
+ '1.0.0-alpha.va1id' ,
33
+ '1.0.0-alpha.0valid' ,
34
+ '1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay' ,
35
+ '1.0.0-rc.1+build.1' ,
36
+ '2.0.0-rc.1+build.123' ,
37
+ '1.2.3-beta' ,
38
+ '10.2.3-DEV-SNAPSHOT' ,
39
+ '1.2.3-SNAPSHOT-123' ,
40
+ '1.0.0' ,
41
+ '2.0.0' ,
42
+ '1.1.7' ,
43
+ '2.0.0+build.1848' ,
44
+ '2.0.1-alpha.1227' ,
45
+ '1.0.0-alpha+beta' ,
46
+ '1.2.3----RC-SNAPSHOT.12.9.1--.12+788' ,
47
+ '1.2.3----R-S.12.9.1--.12+meta' ,
48
+ '1.2.3----RC-SNAPSHOT.12.9.1--.12' ,
49
+ '1.0.0+0.build.1-rc.10000aaa-kk-0.1' ,
50
+ // '99999999999999999999999.999999999999999999.99999999999999999', // Too long
51
+ '1.0.0-0A.is.legal' ,
52
+ ] ;
53
+
54
+ const invalidStrings = [
55
+ '1' ,
56
+ '1.2' ,
57
+ '1.2.3-0123' ,
58
+ '1.2.3-0123.0123' ,
59
+ '1.1.2+.123' ,
60
+ '+invalid' ,
61
+ '-invalid' ,
62
+ '-invalid+invalid' ,
63
+ '-invalid.01' ,
64
+ 'alpha' ,
65
+ 'alpha.beta' ,
66
+ 'alpha.beta.1' ,
67
+ 'alpha.1' ,
68
+ 'alpha+beta' ,
69
+ 'alpha_beta' ,
70
+ 'alpha.' ,
71
+ 'alpha..' ,
72
+ 'beta' ,
73
+ '1.0.0-alpha_beta' ,
74
+ '-alpha.' ,
75
+ '1.0.0-alpha..' ,
76
+ '1.0.0-alpha..1' ,
77
+ '1.0.0-alpha...1' ,
78
+ '1.0.0-alpha....1' ,
79
+ '1.0.0-alpha.....1' ,
80
+ '1.0.0-alpha......1' ,
81
+ '1.0.0-alpha.......1' ,
82
+ '01.1.1' ,
83
+ '1.01.1' ,
84
+ '1.1.01' ,
85
+ '1.2' ,
86
+ '1.2.3.DEV' ,
87
+ '1.2-SNAPSHOT' ,
88
+ '1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788' ,
89
+ '1.2-RC-SNAPSHOT' ,
90
+ '-1.0.3-gamma+b7718' ,
91
+ '+justmeta' ,
92
+ '9.8.7+meta+meta' ,
93
+ '9.8.7-whatever+meta+meta' ,
94
+ '99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12' ,
95
+ '1.0.0-beta@beta' ,
17
96
] ;
18
97
19
98
test ( 'matches semver versions on test' , t => {
20
- for ( const fixture of fixtures ) {
99
+ for ( const fixture of validStrings ) {
21
100
t . regex ( fixture , semverRegex ( ) ) ;
101
+ t . true ( semver . valid ( fixture ) !== null ) ;
102
+
103
+ if ( ! fixture . startsWith ( 'v' ) ) { // Should we trim v prefix?
104
+ t . deepEqual ( fixture . match ( semverRegex ( ) ) , [ fixture ] ) ;
105
+ }
22
106
}
23
107
24
108
t . notRegex ( '0.88' , semverRegex ( ) ) ;
@@ -30,59 +114,18 @@ test('matches semver versions on test', t => {
30
114
test ( 'returns semver on match' , t => {
31
115
t . deepEqual ( '0.0.0' . match ( semverRegex ( ) ) , [ '0.0.0' ] ) ;
32
116
t . deepEqual ( 'foo 0.0.0 bar 0.1.1' . match ( semverRegex ( ) ) , [ '0.0.0' , '0.1.1' ] ) ;
117
+ t . deepEqual ( '1.2.3-alpha.10.beta' . match ( semverRegex ( ) ) , [ '1.2.3-alpha.10.beta' ] ) ;
118
+ t . deepEqual ( '0.0.0-foo-bar alpha.beta.1 1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788 1.0.0-alpha+beta 1.2.3----RC-SNAPSHOT.12.9.1--.12+788 1.2 1.2.3-4' . match ( semverRegex ( ) ) , [ '0.0.0-foo-bar' , '1.0.0-alpha+beta' , '1.2.3----RC-SNAPSHOT.12.9.1--.12+788' , '1.2.3-4' ] ) ;
33
119
} ) ;
34
120
35
121
test ( '#7, does not return tag prefix' , t => {
36
122
t . deepEqual ( 'v0.0.0' . match ( semverRegex ( ) ) , [ '0.0.0' ] ) ;
37
123
} ) ;
38
124
39
125
test ( '#14, does not match sub-strings of longer semver-similar strings, respect [email protected] clause 9' , t => {
40
- // TODO: Some of these are disabled as we need to improve the regex.
41
- const invalidStrings = [
42
- '1' ,
43
- '1.2' ,
44
- // '1.2.3-0123',
45
- // '1.2.3-0123.0123',
46
- // '1.1.2+.123',
47
- '+invalid' ,
48
- '-invalid' ,
49
- '-invalid+invalid' ,
50
- '-invalid.01' ,
51
- 'alpha' ,
52
- 'alpha.beta' ,
53
- 'alpha.beta.1' ,
54
- 'alpha.1' ,
55
- 'alpha+beta' ,
56
- 'alpha_beta' ,
57
- 'alpha.' ,
58
- 'alpha..' ,
59
- 'beta' ,
60
- // '1.0.0-alpha_beta',
61
- '-alpha.' ,
62
- // '1.0.0-alpha..',
63
- // '1.0.0-alpha..1',
64
- // '1.0.0-alpha...1',
65
- // '1.0.0-alpha....1',
66
- // '1.0.0-alpha.....1',
67
- // '1.0.0-alpha......1',
68
- // '1.0.0-alpha.......1',
69
- '01.1.1' ,
70
- '1.01.1' ,
71
- '1.1.01' ,
72
- '1.2' ,
73
- // '1.2.3.DEV',
74
- '1.2-SNAPSHOT' ,
75
- // '1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788',
76
- '1.2-RC-SNAPSHOT' ,
77
- '-1.0.3-gamma+b7718' ,
78
- '+justmeta'
79
- // '9.8.7+meta+meta',
80
- // '9.8.7-whatever+meta+meta',
81
- // '99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12'
82
- ] ;
83
-
84
126
for ( const string of invalidStrings ) {
85
127
t . notRegex ( string , semverRegex ( ) ) ;
128
+ t . true ( semver . valid ( string ) === null ) ;
86
129
}
87
130
} ) ;
88
131
@@ -93,26 +136,27 @@ test('#18, allow 0 as numeric identifier', t => {
93
136
'1.2.0-alpha.10.beta+build.unicorn.rainbow' ,
94
137
'1.2.3-0.10.beta+build.unicorn.rainbow' ,
95
138
'1.2.3-alpha.0.beta+build.unicorn.rainbow' ,
96
- '1.2.3-alpha.10.0+build.unicorn.rainbow'
139
+ '1.2.3-alpha.10.0+build.unicorn.rainbow' ,
97
140
] ) {
98
141
t . regex ( string , semverRegex ( ) ) ;
142
+ t . true ( semver . valid ( string ) !== null ) ;
99
143
}
100
144
} ) ;
101
145
102
146
// If tests take longer than a second, it's stuck on this and we have catatrophic backtracking.
103
147
test ( 'invalid version does not cause catatrophic backtracking' , t => {
104
148
t . regex (
105
149
'v1.1.3-0aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$' ,
106
- semverRegex ( )
150
+ semverRegex ( ) ,
107
151
) ;
108
152
109
- const postfix = '.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' . repeat ( 99999 ) ;
153
+ const postfix = '.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' . repeat ( 99_999 ) ;
110
154
t . regex (
111
155
`v1.1.3-0aa${ postfix } $` ,
112
- semverRegex ( )
156
+ semverRegex ( ) ,
113
157
) ;
114
158
115
- for ( let index = 1 ; index <= 50000 ; index ++ ) {
159
+ for ( let index = 1 ; index <= 50_000 ; index ++ ) {
116
160
const start = Date . now ( ) ;
117
161
const fixture = `0.0.0-0${ '.-------' . repeat ( index ) } @` ;
118
162
semverRegex ( ) . test ( fixture ) ;
@@ -127,4 +171,54 @@ test('invalid version does not cause catatrophic backtracking', t => {
127
171
const difference = Date . now ( ) - start ;
128
172
t . true ( difference < 20 , `Execution time: ${ difference } ` ) ;
129
173
}
174
+
175
+ for ( let index = 1 ; index <= 30 ; index ++ ) {
176
+ // Attack string generated by https://devina.io/redos-checker
177
+ const start = Date . now ( ) ;
178
+ const fixtures = [
179
+ '0.0.1-i' + '--i-' . repeat ( index ) + '\u0000' ,
180
+ '0' + ' 0.1.0-i0' . repeat ( index ) + '.1.1+1' + '1' . repeat ( index ) + 'A' ,
181
+ '1.0.1--' + '-' . repeat ( index ) + '\u0000' ,
182
+ 'g' + ' 0.0.1-i+' . repeat ( index ) + 'a' + 'v0' . repeat ( index ) + '\u0000' ,
183
+ ] ;
184
+ for ( const fixture of fixtures ) {
185
+ semverRegex ( ) . test ( fixture ) ;
186
+ }
187
+
188
+ const difference = Date . now ( ) - start ;
189
+ t . true ( difference < 20 , `Execution time: ${ difference } ` ) ;
190
+ }
191
+
192
+ for ( let index = 1 ; index <= 100 ; index ++ ) {
193
+ const start = Date . now ( ) ;
194
+ const shuffle = array => array . sort ( ( ) => Math . random ( ) - 0.5 ) ;
195
+ // Adapted from https://gist.github.com/6174/6062387
196
+ const rndstr = ( ( ) => {
197
+ const gen = ( min , max ) => max ++ && Array . from ( { length : max - min } ) . map ( ( s , i ) => String . fromCodePoint ( min + i ) ) ;
198
+ const sets = {
199
+ num : gen ( 48 , 57 ) ,
200
+ alphaLower : gen ( 97 , 122 ) ,
201
+ alphaUpper : gen ( 65 , 90 ) ,
202
+ special : [ ...'~!@#$%^&*()_+-=[]{}|;:\'",./<>?' ] ,
203
+ } ;
204
+ function * iter ( length , set ) {
205
+ if ( set . length === 0 ) {
206
+ set = Object . values ( sets ) . flat ( ) ;
207
+ }
208
+
209
+ for ( let i = 0 ; i < length ; i ++ ) {
210
+ yield set [ Math . trunc ( Math . random ( ) * set . length ) ] ;
211
+ }
212
+ }
213
+
214
+ return Object . assign ( ( ( length , ...set ) => [ ...iter ( length , set . flat ( ) ) ] . join ( '' ) ) , sets ) ;
215
+ } ) ( ) ;
216
+ const fuzz = Array . from ( { length : 100 } ) . map ( ( ) => rndstr ( 100 * Math . random ( ) , rndstr . alphaUpper , rndstr . special , rndstr . alphaLower , rndstr . num ) ) ;
217
+ const fixture = shuffle ( Array . from ( { length : index } ) . map ( ( ) => [ validStrings , invalidStrings , fuzz ] ) . flat ( 2 ) ) . join ( ' ' ) ;
218
+
219
+ semverRegex ( ) . test ( fixture ) ;
220
+
221
+ const difference = Date . now ( ) - start ;
222
+ t . true ( difference < 50 , `Execution time: ${ difference } ` ) ;
223
+ }
130
224
} ) ;
0 commit comments