@@ -85,31 +85,57 @@ const fixupNestedPseudo = astNode => {
8585 transformAst ( newRootNode )
8686}
8787
88- // :semver(<version|range>, [selector], [function])
88+ // :semver(<version|range|selector>, [version|range|selector], [function])
89+ // note: the first or second parameter must be a static version or range
8990const fixupSemverSpecs = astNode => {
90- // the first child node contains the version or range, most likely as a tag and a series of
91- // classes. we combine them into a single string here. this is the only required input.
92- const children = astNode . nodes . shift ( ) . nodes
93- const value = children . reduce ( ( res , i ) => `${ res } ${ String ( i ) } ` , '' )
94-
95- // next, if we have 2 nodes left then the user called us with a total of 3. that means the
96- // last one tells us what specific semver function the user is requesting, so we pull that out
97- let semverFunc
98- if ( astNode . nodes . length === 2 ) {
91+ // if we have three nodes, the last is the semver function to use, pull that out first
92+ if ( astNode . nodes . length === 3 ) {
9993 const funcNode = astNode . nodes . pop ( ) . nodes [ 0 ]
10094 if ( funcNode . type === 'tag' ) {
101- semverFunc = funcNode . value
95+ astNode . semverFunc = funcNode . value
96+ } else if ( funcNode . type === 'string' ) {
97+ // a string is always in some type of quotes, we don't want those so slice them off
98+ astNode . semverFunc = funcNode . value . slice ( 1 , - 1 )
99+ } else {
100+ // anything that isn't a tag or a string isn't a function name
101+ throw Object . assign (
102+ new Error ( '`:semver` pseudo-class expects a function name as last value' ) ,
103+ { code : 'ESEMVERFUNC' }
104+ )
105+ }
106+ }
107+
108+ // now if we have 1 node, it's a static value
109+ // istanbul ignore else
110+ if ( astNode . nodes . length === 1 ) {
111+ const semverNode = astNode . nodes . pop ( )
112+ astNode . semverValue = semverNode . nodes . reduce ( ( res , next ) => `${ res } ${ String ( next ) } ` , '' )
113+ } else if ( astNode . nodes . length === 2 ) {
114+ // and if we have two nodes, one of them is a static value and we need to determine which it is
115+ for ( let i = 0 ; i < astNode . nodes . length ; ++ i ) {
116+ const type = astNode . nodes [ i ] . nodes [ 0 ] . type
117+ // the type of the first child may be combinator for ranges, such as >14
118+ if ( type === 'tag' || type === 'combinator' ) {
119+ const semverNode = astNode . nodes . splice ( i , 1 ) [ 0 ]
120+ astNode . semverValue = semverNode . nodes . reduce ( ( res , next ) => `${ res } ${ String ( next ) } ` , '' )
121+ astNode . semverPosition = i
122+ break
123+ }
124+ }
125+
126+ if ( typeof astNode . semverValue === 'undefined' ) {
127+ throw Object . assign (
128+ new Error ( '`:semver` pseudo-class expects a static value in the first or second position' ) ,
129+ { code : 'ESEMVERVALUE' }
130+ )
102131 }
103132 }
104133
105- // now if there's a node left, that node is our selector. since that is the last remaining
106- // child node, we call fixupAttr on ourselves so that the attribute selectors get parsed
134+ // if we got here, the last remaining child should be attribute selector
107135 if ( astNode . nodes . length === 1 ) {
108136 fixupAttr ( astNode )
109137 } else {
110- // we weren't provided a selector, so we default to `[version]`. note, there's no default
111- // operator here. that's because we don't know yet if the user has provided us a version
112- // or range to assert against
138+ // if we don't have a selector, we default to `[version]`
113139 astNode . attributeMatcher = {
114140 insensitive : false ,
115141 attribute : 'version' ,
@@ -118,8 +144,6 @@ const fixupSemverSpecs = astNode => {
118144 astNode . lookupProperties = [ ]
119145 }
120146
121- astNode . semverFunc = semverFunc
122- astNode . semverValue = value
123147 astNode . nodes . length = 0
124148}
125149
0 commit comments