@@ -291,11 +291,90 @@ class Results {
291
291
}
292
292
293
293
semverPseudo ( ) {
294
- if ( ! this . currentAstNode . semverValue ) {
294
+ const { lookupProperties, attributeMatcher, semverFunc, semverValue } = this . currentAstNode
295
+ if ( ! semverValue || ( ! semver . valid ( semverValue ) && ! semver . validRange ( semverValue ) ) ) {
295
296
return this . initialItems
296
297
}
297
- return this . initialItems . filter ( node =>
298
- semver . satisfies ( node . version , this . currentAstNode . semverValue ) )
298
+
299
+ const valueIsVersion = ! ! semver . valid ( semverValue )
300
+
301
+ const nodeMatches = ( node , obj ) => {
302
+ // if we already have an operator, the user provided some test as part of the selector
303
+ // we evaluate that first because if it fails we don't want this node anyway
304
+ if ( attributeMatcher . operator ) {
305
+ if ( ! attributeMatch ( attributeMatcher , obj ) ) {
306
+ // if the initial operator doesn't match, we're done
307
+ return false
308
+ }
309
+ }
310
+
311
+ // read the value from the provided object
312
+ const foundValue = obj [ attributeMatcher . qualifiedAttribute ]
313
+ // if the value is not a valid semver version or range, we can't match so we're done
314
+ if ( ! foundValue || ( ! semver . valid ( foundValue ) && ! semver . validRange ( foundValue ) ) ) {
315
+ return false
316
+ }
317
+ const foundIsVersion = ! ! semver . valid ( foundValue )
318
+
319
+ // make a new matcher inline to do the semver checks
320
+ const semverMatcher = {
321
+ qualifiedAttribute : attributeMatcher . qualifiedAttribute ,
322
+ value : semverValue ,
323
+ }
324
+
325
+ // if we were provided an operator function, see if we can use that
326
+ if ( semverFunc ) {
327
+ // these functions each need 2 versions _not_ ranges or they'll throw, so we skip any nodes
328
+ // that would cause an error to be thrown
329
+ if ( [ 'eq' , 'neq' , 'gt' , 'gte' , 'lt' , 'lte' ] . includes ( semverFunc ) ) {
330
+ if ( ! valueIsVersion || ! foundIsVersion ) {
331
+ return false
332
+ }
333
+ }
334
+
335
+ // since versions are valid ranges, any other supported function is fine
336
+ semverMatcher . operator = `semver.${ semverFunc } `
337
+ } else {
338
+ // otherwise infer what type of operator we need based on the types of the two values
339
+ if ( foundIsVersion && valueIsVersion ) {
340
+ // 2 versions
341
+ semverMatcher . operator = 'semver.eq'
342
+ } else if ( ! foundIsVersion && ! valueIsVersion ) {
343
+ // 2 ranges
344
+ semverMatcher . operator = 'semver.intersects'
345
+ } else {
346
+ // 1 version, 1 range
347
+ semverMatcher . operator = 'semver.satisfies'
348
+ }
349
+ }
350
+
351
+ return attributeMatch ( semverMatcher , obj )
352
+ }
353
+
354
+ return this . initialItems . filter ( ( node ) => {
355
+ // no lookupProperties just means its a top level property, see if it matches
356
+ if ( ! lookupProperties . length ) {
357
+ return nodeMatches ( node , node . package )
358
+ }
359
+
360
+ // this code is mostly duplicated from attrPseudo to traverse into the package until we get
361
+ // to our deepest requested object
362
+ let objs = [ node . package ]
363
+ for ( const prop of lookupProperties ) {
364
+ if ( prop === arrayDelimiter ) {
365
+ objs = objs . flat ( )
366
+ continue
367
+ }
368
+
369
+ objs = objs . flatMap ( obj => obj [ prop ] || [ ] )
370
+ const noAttr = objs . every ( obj => ! obj )
371
+ if ( noAttr ) {
372
+ return false
373
+ }
374
+
375
+ return objs . some ( obj => nodeMatches ( node , obj ) )
376
+ }
377
+ } )
299
378
}
300
379
301
380
typePseudo ( ) {
@@ -345,6 +424,50 @@ const attributeOperators = {
345
424
'$=' ( { attr, value, insensitive } ) {
346
425
return attr . endsWith ( value )
347
426
} ,
427
+ // semver comparisons, these are internal operators used by semverPseudo
428
+ // functions that take 2 versions
429
+ 'semver.eq' ( { attr, value } ) {
430
+ return semver . eq ( attr , value )
431
+ } ,
432
+ 'semver.neq' ( { attr, value } ) {
433
+ return semver . neq ( attr , value )
434
+ } ,
435
+ 'semver.gt' ( { attr, value } ) {
436
+ return semver . gt ( attr , value )
437
+ } ,
438
+ 'semver.gte' ( { attr, value } ) {
439
+ return semver . gte ( attr , value )
440
+ } ,
441
+ 'semver.lt' ( { attr, value } ) {
442
+ return semver . lt ( attr , value )
443
+ } ,
444
+ 'semver.lte' ( { attr, value } ) {
445
+ return semver . lte ( attr , value )
446
+ } ,
447
+ // functions that take 2 ranges
448
+ 'semver.intersects' ( { attr, value } ) {
449
+ return semver . intersects ( attr , value )
450
+ } ,
451
+ 'semver.subset' ( { attr, value } ) {
452
+ return semver . subset ( attr , value )
453
+ } ,
454
+ // functions that take 1 version and 1 range, for these we infer the order to pass
455
+ // parameters on the user's behalf
456
+ 'semver.gtr' ( { attr, value } ) {
457
+ return semver . valid ( attr )
458
+ ? semver . gtr ( attr , value )
459
+ : semver . gtr ( value , attr )
460
+ } ,
461
+ 'semver.ltr' ( { attr, value } ) {
462
+ return semver . valid ( attr )
463
+ ? semver . ltr ( attr , value )
464
+ : semver . ltr ( value , attr )
465
+ } ,
466
+ 'semver.satisfies' ( { attr, value } ) {
467
+ return semver . valid ( attr )
468
+ ? semver . satisfies ( attr , value )
469
+ : semver . satisfies ( value , attr )
470
+ } ,
348
471
}
349
472
350
473
const attributeOperator = ( { attr, value, insensitive, operator } ) => {
@@ -358,6 +481,13 @@ const attributeOperator = ({ attr, value, insensitive, operator }) => {
358
481
if ( insensitive ) {
359
482
attr = attr . toLowerCase ( )
360
483
}
484
+
485
+ if ( ! attributeOperators [ operator ] ) {
486
+ throw Object . assign (
487
+ new Error ( `\`${ operator } \` is not a supported operator.` ) ,
488
+ { code : 'EQUERYINVALIDOPERATOR' } )
489
+ }
490
+
361
491
return attributeOperators [ operator ] ( {
362
492
attr,
363
493
insensitive,
0 commit comments