11import test from 'ava' ;
2+ import semver from 'semver' ;
23import semverRegex from './index.js' ;
34
4- const fixtures = [
5+ const validStrings = [
56 '0.0.0' ,
67 '0.10.0' ,
78 'v1.0.0' ,
89 '0.0.0-foo' ,
10+ '0.0.0-foo-bar-baz' ,
911 '1.2.3-4' ,
1012 '2.7.2+asdf' ,
1113 '1.2.3-a.b.c.10.d.5' ,
1214 '2.7.2-foo+bar' ,
1315 '1.2.3-alpha.10.beta' ,
1416 '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' ,
1796] ;
1897
1998test ( 'matches semver versions on test' , t => {
20- for ( const fixture of fixtures ) {
99+ for ( const fixture of validStrings ) {
21100 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+ }
22106 }
23107
24108 t . notRegex ( '0.88' , semverRegex ( ) ) ;
@@ -30,59 +114,18 @@ test('matches semver versions on test', t => {
30114test ( 'returns semver on match' , t => {
31115 t . deepEqual ( '0.0.0' . match ( semverRegex ( ) ) , [ '0.0.0' ] ) ;
32116 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' ] ) ;
33119} ) ;
34120
35121test ( '#7, does not return tag prefix' , t => {
36122 t . deepEqual ( 'v0.0.0' . match ( semverRegex ( ) ) , [ '0.0.0' ] ) ;
37123} ) ;
38124
39125test ( '#14, does not match sub-strings of longer semver-similar strings, respect semver@2.0.0 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-
84126 for ( const string of invalidStrings ) {
85127 t . notRegex ( string , semverRegex ( ) ) ;
128+ t . true ( semver . valid ( string ) === null ) ;
86129 }
87130} ) ;
88131
@@ -93,26 +136,27 @@ test('#18, allow 0 as numeric identifier', t => {
93136 '1.2.0-alpha.10.beta+build.unicorn.rainbow' ,
94137 '1.2.3-0.10.beta+build.unicorn.rainbow' ,
95138 '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' ,
97140 ] ) {
98141 t . regex ( string , semverRegex ( ) ) ;
142+ t . true ( semver . valid ( string ) !== null ) ;
99143 }
100144} ) ;
101145
102146// If tests take longer than a second, it's stuck on this and we have catatrophic backtracking.
103147test ( 'invalid version does not cause catatrophic backtracking' , t => {
104148 t . regex (
105149 '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 ( ) ,
107151 ) ;
108152
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 ) ;
110154 t . regex (
111155 `v1.1.3-0aa${ postfix } $` ,
112- semverRegex ( )
156+ semverRegex ( ) ,
113157 ) ;
114158
115- for ( let index = 1 ; index <= 50000 ; index ++ ) {
159+ for ( let index = 1 ; index <= 50_000 ; index ++ ) {
116160 const start = Date . now ( ) ;
117161 const fixture = `0.0.0-0${ '.-------' . repeat ( index ) } @` ;
118162 semverRegex ( ) . test ( fixture ) ;
@@ -127,4 +171,54 @@ test('invalid version does not cause catatrophic backtracking', t => {
127171 const difference = Date . now ( ) - start ;
128172 t . true ( difference < 20 , `Execution time: ${ difference } ` ) ;
129173 }
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+ }
130224} ) ;
0 commit comments