-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Integrate with a CSS parser for selector parsing #1086
Integrate with a CSS parser for selector parsing #1086
Conversation
ab6a603
to
38adb22
Compare
@@ -433,22 +435,15 @@ describeWithDOM('mount', () => { | |||
expect(wrapper.find('.row + .row')).to.have.lengthOf(1); | |||
}); | |||
|
|||
// React 15.2 warns when setting a non valid prop to an DOM element | |||
describeIf(REACT013 || REACT014, 'unauthorized dom props', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test was kind of a false positive. You could still query unauthorized DOM props by using the object syntax, and AFAICT it was only failing to find them because of how it was parsing the selector.
React <0.14 didn't just ignore syntactically invalid attributes, it ignored attributes that it didn't have whitelisted. To implement this behavior as it should be implemented we'd need to integrate that same whitelist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add that whitelist in the react 0.14 and 0.13 adapters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, OK to do in a follow up?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, totes ok to do in a followup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pausing my review pending answers about the quoted string question.
packages/enzyme/src/Utils.js
Outdated
@@ -308,14 +308,6 @@ export function coercePropValue(propName, propValue) { | |||
|
|||
const trimmedValue = propValue.trim(); | |||
|
|||
// if propValue includes quotes, it should be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? The fact that selectors can omit quotes is imo very problematic; enforcing strings ensures that selectors used will be unambiguous.
@@ -155,13 +153,6 @@ describe('RSTTraversal', () => { | |||
expect(nodeHasProperty(<div foo={-Infinity} />, 'foo', '-Infinity')).to.equal(true); | |||
expect(nodeHasProperty(<div foo={0} />, 'foo', '-Infinity')).to.equal(false); | |||
}); | |||
|
|||
it('should throw when un unquoted string is passed in', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tagging this line too - i think the purpose of requiring strings here was so people would distinguish between numbers and strings, for example.
Regardless of what mistakes React 16 has made, older Reacts do, and non-Reacts might, still want to distinguish these on host components - and they'll always need distinguishing on custom components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the spec, unquoted values must be valid identifiers or strings. The selectors [foo=bar]
and [foo="bar"]
are thus parsed identically. A spec-compliant parser would return identical tokens for both of those selectors, and that's indeed what scalpel
does.
Using numbers as the attribute value is a violation of the spec, e.g., [foo=42]
, since valid CSS identifiers cannot start with numbers. Implementing this behavior would require using a selector parser that does not conform to the selector spec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right - the problem is that we can't use spec-compliant selectors, because we're not selecting HTML - we're selecting from an RST, which has non-string values (HTML only has strings).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In other words, we can only use the actual selector spec if we're restricting selectors to DOM elements - so enzyme's selection is always going to have to be different than the web spec (we test react native too, for example)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, I can just fork scalpel
and redefine the grammar to require unquoted values be numerical. I'm not super familiar with writing parser grammars but I think I can do it 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it'd be even better if we could make a new package that avoids forking and instead directly depends on scalpel, but i'm not sure how hard that'd be :-/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It wouldn't really be possible, this requires making a change directly to the grammar for attribute values and there's no way to do that without changing grammar.ne
.
@ljharb here's a PR to my fork of I was going to publish this as |
@ljharb I've integrated the new |
throw new TypeError( | ||
`Enzyme::Unable to parse selector '[${propName}=${propValue}]'. ` + | ||
`Perhaps you forgot to escape a string? Try '[${propName}="${trimmedValue}"]' instead.`, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any selector that is unparsable will fail during the parsing step now.
576310d
to
f976a3e
Compare
@ljharb @lelandrichardson I think this is ready for a full review. I marked all the issues this should resolve in the top comment and added tests for them. |
return nodeHasProperty(node, token.name, token.value); | ||
case PSEUDO_ELEMENT: | ||
case PSEUDO_CLASS: | ||
throw new Error('Enzyme::Selector does not support psuedo-element or psuedo-class selectors.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding support for pseudo-selectors will be really easy. For example, implementing :not
(#460). I figure this can be done in a follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference, I implemented :not
with this same approach here: https://github.com/aweary/react-tester/blob/master/src/selector-parser.js#L91-L102
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Followups for these things are great; this PR's big enough already :-)
* @example '[data-foo=foo]' matches <div data-foo="foo" /> | ||
*/ | ||
case ATTRIBUTE_VALUE: | ||
return nodeHasProperty(node, token.name, token.value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized the parser returns the operator used in attribute value selectors, so we can now support selectors like [foo|="bar"]
and [foo^="bar"]
@ljharb do you think we'll be able to get this in for v3? |
9e1ead2
to
8ddce58
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this is looking great.
Aside from the breaking change of our helper functions, is there anything in the main shallow/mount API that used to work, but will stop working as a result of this change? If so, we'll need to include it in our v3 migration guide.
}); | ||
|
||
it('throws for malformed selectors', () => { | ||
expect(() => fn('div[data-name="xyz"')).to.throw(/Enzyme::Selector received what appears to be a malformed string selector/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this no longer throw (somewhere)? it's a malformed selector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does throw, but since the new rst-selector-parser
manages all parsing this should be covered by the unit tests for the package. I figure at this point we'd be testing the behavior of a dependency with its own tests.
But I'll add back a test that ensures that we catch the general parsing error and re-throw something more specific. Then I think it's safe to assume invalid selectors will throw, and if the rst-selector-parser
coverage is missing a case we can add it there.
On that note, I'd be happy to include rst-selector-parser
as part of this monorepo if we want to.
it('should parse false as a literal', () => { | ||
const node = $(<div foo />); | ||
|
||
expect(nodeHasProperty(node, 'foo', 'true')).to.equal(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this test not still be valid for true
instead of 'true'
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should, I'll add that case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added test case for booleans: https://github.com/airbnb/enzyme/pull/1086/files#diff-67b0df7735c607bdef49d8a20292e57aR71
expect(nodeHasProperty(<div foo={2} />, 'foo', '-0')).to.equal(false); | ||
expect(nodeHasProperty(<div foo={0} />, 'foo', 0)).to.equal(true); | ||
expect(nodeHasProperty(<div foo={0} />, 'foo', +0)).to.equal(true); | ||
expect(nodeHasProperty(<div foo={-0} />, 'foo', -0)).to.equal(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we add test cases that -0
does not match 0
, and 0
does not match -0
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
expect(nodeHasProperty(<div foo={0} />, 'foo', +0)).to.equal(true); | ||
expect(nodeHasProperty(<div foo={-0} />, 'foo', -0)).to.equal(true); | ||
expect(nodeHasProperty(<div foo={1} />, 'foo', 0)).to.equal(false); | ||
expect(nodeHasProperty(<div foo={2} />, 'foo', -0)).to.equal(false); | ||
}); | ||
|
||
it('should work with empty strings', () => { | ||
expect(nodeHasProperty(<div foo={''} />, 'foo', '')).to.equal(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for funsies, let's add foo=""
also?
|
||
expect(() => nodeHasProperty(node, 'title', 'foo')).to.throw(); | ||
expect(nodeHasProperty(<div foo={Infinity} />, 'foo', Infinity)).to.equal(true); | ||
expect(nodeHasProperty(<div foo={Infinity} />, 'foo', +Infinity)).to.equal(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's add a case where Infinity
fails to match a prop value of Infinity
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd also be good to ensure NaN and Infinity don't equate, since both are not finite
* @param {Token} token | ||
*/ | ||
function nodeMatchesToken(node, token) { | ||
if (node === null || typeof node === 'string') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you elaborate on why a string node wouldn't match? is there no selector that can match text?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, with the current grammar there's no way to match a string literal. foo
would match an element of type foo
. We could augment the grammar to allow something like "foo"
but I'm not sure how useful that really is.
return nodeHasProperty(node, token.name, token.value); | ||
case PSEUDO_ELEMENT: | ||
case PSEUDO_CLASS: | ||
throw new Error('Enzyme::Selector does not support psuedo-element or psuedo-class selectors.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Followups for these things are great; this PR's big enough already :-)
packages/enzyme/src/selectors.js
Outdated
* @param {Function|Object|String} selector | ||
*/ | ||
export function buildPredicate(selector) { | ||
switch (typeof selector) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fwiw, this bypasses the engine optimization when typeof foo
is repeated. This would be better as an if
/else
, for a number of reasons.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ljharb can you elaborate on what optimization this bypasses? I'm not familiar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
engines are able to do typeof foo === 'some string'
checks very very fast; when you store typeof foo
in a variable (or a binding, as here) it has to go the slower path.
packages/enzyme/src/selectors.js
Outdated
// constructor | ||
return node => node && node.type === selector; | ||
case 'object': | ||
if (!Array.isArray(selector) && selector !== null && !isEmpty(selector)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does lodash return for isEmpty(null)
?
if we're keeping !== null
for perf, then we should be checking that first, before isArray
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll figure out what it returns and update this. I just copied what was already there FWIW: https://github.com/airbnb/enzyme/blob/c15dd479684a9e3423d3efb5aea592fcfde1a842/packages/enzyme/src/RSTTraversal.js#L119
packages/enzyme/src/selectors.js
Outdated
export function reduceTreeBySelector(selector, wrapper) { | ||
const root = wrapper.getNodeInternal(); | ||
let results = []; | ||
switch (typeof selector) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here wrt typeof and if/else
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ljharb thanks! I'll move those changes today.
I think this should be backward compatible. There may be breaking changes if we were silently incorrectly parsing and applying a selector before, but as far as expected behavior goes it should be consistent.
}); | ||
|
||
it('throws for malformed selectors', () => { | ||
expect(() => fn('div[data-name="xyz"')).to.throw(/Enzyme::Selector received what appears to be a malformed string selector/); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does throw, but since the new rst-selector-parser
manages all parsing this should be covered by the unit tests for the package. I figure at this point we'd be testing the behavior of a dependency with its own tests.
But I'll add back a test that ensures that we catch the general parsing error and re-throw something more specific. Then I think it's safe to assume invalid selectors will throw, and if the rst-selector-parser
coverage is missing a case we can add it there.
On that note, I'd be happy to include rst-selector-parser
as part of this monorepo if we want to.
it('should parse false as a literal', () => { | ||
const node = $(<div foo />); | ||
|
||
expect(nodeHasProperty(node, 'foo', 'true')).to.equal(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should, I'll add that case.
@@ -433,22 +435,15 @@ describeWithDOM('mount', () => { | |||
expect(wrapper.find('.row + .row')).to.have.lengthOf(1); | |||
}); | |||
|
|||
// React 15.2 warns when setting a non valid prop to an DOM element | |||
describeIf(REACT013 || REACT014, 'unauthorized dom props', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, OK to do in a follow up?
packages/enzyme/package.json
Outdated
@@ -39,6 +39,7 @@ | |||
"object.assign": "^4.0.4", | |||
"object.entries": "^1.0.4", | |||
"raf": "^3.3.2", | |||
"rst-selector-parser": "^2.2.0", | |||
"uuid": "^3.1.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! I'll remove it.
* @param {Token} token | ||
*/ | ||
function nodeMatchesToken(node, token) { | ||
if (node === null || typeof node === 'string') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, with the current grammar there's no way to match a string literal. foo
would match an element of type foo
. We could augment the grammar to allow something like "foo"
but I'm not sure how useful that really is.
packages/enzyme/src/selectors.js
Outdated
// constructor | ||
return node => node && node.type === selector; | ||
case 'object': | ||
if (!Array.isArray(selector) && selector !== null && !isEmpty(selector)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll figure out what it returns and update this. I just copied what was already there FWIW: https://github.com/airbnb/enzyme/blob/c15dd479684a9e3423d3efb5aea592fcfde1a842/packages/enzyme/src/RSTTraversal.js#L119
8ddce58
to
8e3a352
Compare
@ljharb I think I've addressed all of your feedback! |
it('simple adjacent', () => { | ||
const wrapper = renderMethod( | ||
<div> | ||
<div className="to-find" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
something worth testing is to have children of a node that are in separate arrays but adjacent to one another:
<div>
<div className="a" />
{[<div key="0" className="b" />]}
</div>
With this you can assert:
expect(wrapper.find('.a + .b'))..to.have.lengthOf(1)
* @param {*} root | ||
* @param {*} targetNode | ||
*/ | ||
export function findParentNode(root, targetNode) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just curious.... RSTTraversal.js has a parentsOfNode
function that you could potentially use for this? Did it not work when you tried or did you not know about it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did see that, and it wasn't working for me. I didn't spend a lot of time trying to fix it though since I wasn't really following how it was implemented. I'm not actually sure how it's working at all, since it calls reverse()
on the result of pathToNode
, but pathToNode
returns null
, which is the error I was getting
TypeError: Cannot read property 'reverse' of null
at parentsOfNode (packages/enzyme/build/RSTTraversal.js:121:10)
It's also more complicated than we need for selector matching since AFAICT it finds all parents, not just the direct parent. If we add a parent
pointer per discussions in #1085 this utility could go away altogether.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that that error is a pre-existing bug in enzyme 2 - #410
* by scalpel. | ||
* @param {String} selector | ||
*/ | ||
function safelyGenerateTokens(selector) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feels weird to call this safely{thing}
but still have it throw
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah throwing is the safest thing ever! Unsafe things are what don't throw :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was thinking "safe" because we explicitly throw our own error instead of letting the parser crash, and know that the tokens don't require additional validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wow this PR is SOOOO GOOOD. Thank you so much for doing this. Looks like it was a ton of work to do right but this is so much better than what we had. I had a couple of minor comments, but nothing blocking. Once Jordan signs off, let's get this in!
Oh, also, sorry this took me so long to look at! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM once the remaining comments have been addressed
expect(nodeHasProperty(<div foo={null} />, 'foo', 'null')).to.equal(true); | ||
expect(nodeHasProperty(<div foo={0} />, 'foo', 'null')).to.equal(false); | ||
expect(nodeHasProperty(<div foo={null} />, 'foo', null)).to.equal(true); | ||
expect(nodeHasProperty(<div foo={0} />, 'foo', null)).to.equal(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also add a pair of assertions that null won't equal undefined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the expected behavior with undefined
? It looks like it returns whether nodeProps
has an own property matching the name, which means undefined
ends up matching since foo
exists.
Is that expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nodeHasProperty(<div foo={undefined} />, 'foo', undefined)
should be true
, nodeHasProperty(<div foo={undefined} />, 'foo', null)
should be false
, nodeHasProperty(<div foo={null} />, 'foo', undefined)
should be false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now nodeHasProperty(<div foo={null} />, 'foo', undefined)
returns true
because it's defaulting to the own-property check. I think this behavior existed before this PR too, is that a bug? I'm not sure if that will break some existing tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ping @ljharb ^ I think that's the last point of clarification I need before this is good to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the interest of time, i'm going to move forward w/ this PR. I've verified that this is the current behavior. If we would like to clarify it / change it in an upcoming PR, i'm fine with that. @aweary thanks for being so thorough here.
|
||
expect(() => nodeHasProperty(node, 'title', 'foo')).to.throw(); | ||
expect(nodeHasProperty(<div foo={Infinity} />, 'foo', Infinity)).to.equal(true); | ||
expect(nodeHasProperty(<div foo={Infinity} />, 'foo', +Infinity)).to.equal(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd also be good to ensure NaN and Infinity don't equate, since both are not finite
@@ -433,22 +435,15 @@ describeWithDOM('mount', () => { | |||
expect(wrapper.find('.row + .row')).to.have.lengthOf(1); | |||
}); | |||
|
|||
// React 15.2 warns when setting a non valid prop to an DOM element | |||
describeIf(REACT013 || REACT014, 'unauthorized dom props', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, totes ok to do in a followup
088d3c9
to
aadbc4a
Compare
@lelandrichardson a quick heads up, to get the tests passing I had to make a change to the React 16 adapter (a79e150) since the internal instance key was changed in facebook/react@9921e57 |
@aweary sounds good. thanks. |
I think I've addressed all the feedback, with the exception of my question here #1086 (comment) about |
It looks like the Karma tests are failing intermittently. I restarted the build a couple times on Travis and it eventually worked 🤷♂️ |
@aweary do you have a link to a build where it failed? or the error message? |
@lelandrichardson the last build had the failure with the karma tests and React 16: https://travis-ci.org/airbnb/enzyme/jobs/274910583 It didn't mark the whole build as failed since it's considered an allowed failure, but that's the same error I was seeing before. It looks like some kind of timeout issue. |
Resolves #694, #693, #547, #1082, #1019, #1078, #1086, #283
This PR integrates with rst-selector-parser a fork of scalpel which is a CSS parser implemented with nearley. This fork implements a custom grammar requiring quoted string attribute values, allowing unquoted numbers, booleans, and other primitives, and supports selectors in any order.
Major Changes
buildPredicate
is exported by theselectors
module, the API remains the sameComplexSelector
is no longer needed. Instead theselectors
module exports areduceTreeBySelector
which manages traversal + matching with simple selectors and combinators. Both the shallow and mount wrappers use the samebuildPredicate
andfindWhereUnwrapped
API so there's no need to have a class for selector parsing.Pending Changes
These are changes that I still need to make before this can be merged.
ComplexSelector
and all utility methods that were only used byComplexSelector
buildPredicate
PSEUDO_CLASS
andPSEUDO_ELEMENT
selectors