@@ -85,31 +85,57 @@ const fixupNestedPseudo = astNode => {
85
85
transformAst ( newRootNode )
86
86
}
87
87
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
89
90
const 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 ) {
99
93
const funcNode = astNode . nodes . pop ( ) . nodes [ 0 ]
100
94
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
+ )
102
131
}
103
132
}
104
133
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
107
135
if ( astNode . nodes . length === 1 ) {
108
136
fixupAttr ( astNode )
109
137
} 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]`
113
139
astNode . attributeMatcher = {
114
140
insensitive : false ,
115
141
attribute : 'version' ,
@@ -118,8 +144,6 @@ const fixupSemverSpecs = astNode => {
118
144
astNode . lookupProperties = [ ]
119
145
}
120
146
121
- astNode . semverFunc = semverFunc
122
- astNode . semverValue = value
123
147
astNode . nodes . length = 0
124
148
}
125
149
0 commit comments