From 5f5387b2202f12728a71ee82f1406cf5f983b5f9 Mon Sep 17 00:00:00 2001 From: Gajus Kuizinas Date: Sat, 2 Sep 2017 12:22:53 +0300 Subject: [PATCH] feat: add index selector (fixes #18) --- README.md | 6 ++++-- src/expressions.js | 2 +- src/parsers/parseQuantifierExpression.js | 7 +++++-- src/subroutines/selectSubroutine.js | 16 ++++++++-------- src/types.js | 4 ++-- test/surgeon/subroutines/selectSubroutine.js | 4 ++-- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 38a485d..0ee17c8 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,8 @@ A *quantifier expression* is defined using the following syntax. |Greedy quantifier|`{n,}` where `n >= 0`| |Greedy quantifier|`{,m}` where `m >= 1`| +A *quantifier expression* can be appended a node selector `[i]`, e.g. `{0,}[1]`. This allows to return the first node from the result set. + > If this looks familiar, its because I have adopted the syntax from regular expression language. However, unlike in regular expression, a quantifier in the context of Surgeon selector will produce an error (`SelectSubroutineUnexpectedResultCountError`) if selector result length is out of the quantifier range. Examples: @@ -212,8 +214,8 @@ x('select .foo {1,}'); x('select .foo {0,5}'); // Selects 1 node. -// Result is the matching element (or `null`). -x('select .foo {0,1}'); +// Result is the first match in the result set (or `null`). +x('select .foo {0,}[0]'); ``` diff --git a/src/expressions.js b/src/expressions.js index 6e6a39f..f32ba80 100644 --- a/src/expressions.js +++ b/src/expressions.js @@ -4,4 +4,4 @@ * @see https://github.com/gajus/surgeon#quantifier-expression * @see https://www.regex101.com/r/k3IuC4/2 */ -export const quantifierExpression = /^\{(\d+?)(,?)(-?\d+)?\}?$/; +export const quantifierExpression = /^\{(\d+?)(,?)(-?\d+)?\}(?:\[(\d+)\])?$/; diff --git a/src/parsers/parseQuantifierExpression.js b/src/parsers/parseQuantifierExpression.js index 47e5c55..278cf0d 100644 --- a/src/parsers/parseQuantifierExpression.js +++ b/src/parsers/parseQuantifierExpression.js @@ -8,8 +8,9 @@ import { } from '../expressions'; type ParsedQuantifierExpressionType = {| - max: number, - min: number + +index: number | null, + +max: number, + +min: number |}; export default (selector: string): ParsedQuantifierExpressionType => { @@ -21,11 +22,13 @@ export default (selector: string): ParsedQuantifierExpressionType => { if (quantifier[2] === ',') { return { + index: quantifier[4] ? Number(quantifier[4]) : null, max: quantifier[3] ? Number(quantifier[3]) : Infinity, min: Number(quantifier[1]) }; } else { return { + index: quantifier[2] ? Number(quantifier[2]) : null, max: Number(quantifier[1]), min: Number(quantifier[1]) }; diff --git a/src/subroutines/selectSubroutine.js b/src/subroutines/selectSubroutine.js index a0bf54b..329dd8c 100644 --- a/src/subroutines/selectSubroutine.js +++ b/src/subroutines/selectSubroutine.js @@ -28,22 +28,22 @@ const createQuantifier = (quantifierExpression?: string): SelectSubroutineQuanti if (quantifierTokens.max === 1) { quantifier = { + index: 0, max: 1, - min: 0, - multiple: false + min: 0 }; } else { quantifier = { + index: quantifierTokens.index, max: typeof quantifierTokens.max === 'undefined' ? Infinity : quantifierTokens.max, - min: typeof quantifierTokens.min === 'undefined' ? 0 : quantifierTokens.min, - multiple: true + min: typeof quantifierTokens.min === 'undefined' ? 0 : quantifierTokens.min }; } } else { quantifier = { + index: 0, max: 1, - min: 1, - multiple: false + min: 1 }; } @@ -69,10 +69,10 @@ const selectSubroutine: SubroutineType = (subject, [cssSelector, quantifierExpre throw new SelectSubroutineUnexpectedResultCountError(matches.length, quantifier); } - if (quantifier.multiple === true) { + if (quantifier.index === null) { return matches; } else { - return matches[0] || new FinalResultSentinel(null); + return matches[quantifier.index] || new FinalResultSentinel(null); } }; diff --git a/src/types.js b/src/types.js index 8e0187c..9862ebc 100644 --- a/src/types.js +++ b/src/types.js @@ -21,9 +21,9 @@ export type EvaluatorType = {| export type SubroutineType = (subject: mixed, parameters: Array, bindle: Object) => mixed; export type SelectSubroutineQuantifierType = {| + +index: number | null, +max: number, - +min: number, - +multiple: boolean + +min: number |}; export type UserConfigurationType = { diff --git a/test/surgeon/subroutines/selectSubroutine.js b/test/surgeon/subroutines/selectSubroutine.js index 7e34e26..a9e59f8 100644 --- a/test/surgeon/subroutines/selectSubroutine.js +++ b/test/surgeon/subroutines/selectSubroutine.js @@ -30,7 +30,7 @@ test('returns a single result when expecting at most 1 result', (t): void => { querySelectorAll }; - const result = selectSubroutine(null, ['.foo', '{0,1}'], {evaluator}); + const result = selectSubroutine(null, ['.foo', '{0,1}[0]'], {evaluator}); t.true(result === 'foo'); }); @@ -58,7 +58,7 @@ test('returns FinalResultSentinel(null) when expecting at most 1 result', (t): v querySelectorAll }; - const result = selectSubroutine(null, ['.foo', '{0,1}'], {evaluator}); + const result = selectSubroutine(null, ['.foo', '{0,1}[0]'], {evaluator}); t.true(result instanceof FinalResultSentinel);