Skip to content

Commit

Permalink
:has() should not have any kind of forgiving behavior
Browse files Browse the repository at this point in the history
Recent CSS spec updated :has() to not be forgiving. This is to address
JQuery issues. With that said, we do not offer true forgiving anyways,
just comma forgiveness. As CSS will not be allowing forgiveness for
relative selectors, we will not continue supporting it.
  • Loading branch information
facelessuser committed Feb 10, 2023
1 parent 792d566 commit 02ca817
Show file tree
Hide file tree
Showing 6 changed files with 17 additions and 96 deletions.
8 changes: 3 additions & 5 deletions docs/src/markdown/about/changelog.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# Changelog

## 2.5
## 2.4

- **NEW**: Update to support changes related to `:lang()` in the official CSS spec. `:lang("")` should match unspecified
languages, e.g. `lang=""`, but not `lang=und`.

## 2.4

- **NEW**: `:nth-child()` and `:nth-last-child()` will forgive irregular comma usage.
- **NEW**: Only `:is()` and `:where()` should allow forgiving selector lists according to latest CSS (as far as Soup
Sieve supports "forgiving" which is limited to empty selectors).
- **NEW**: Formally drop Python 3.6.

## 2.3.2.post1
Expand Down
2 changes: 1 addition & 1 deletion soupsieve/__meta__.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,5 @@ def parse_version(ver: str) -> Version:
return Version(major, minor, micro, release, pre, post, dev)


__version_info__ = Version(2, 5, 0, "final", post=1)
__version_info__ = Version(2, 4, 0, "final")
__version__ = __version_info__._get_canonical()
31 changes: 8 additions & 23 deletions soupsieve/css_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ def parse_pseudo_nth(
if postfix == '_child':
if m.group('of'):
# Parse the rest of `of S`.
nth_sel = self.parse_selectors(iselector, m.end(0), FLG_PSEUDO | FLG_OPEN | FLG_FORGIVE)
nth_sel = self.parse_selectors(iselector, m.end(0), FLG_PSEUDO | FLG_OPEN)
else:
# Use default `*|*` for `of S`.
nth_sel = CSS_NTH_OF_S_DEFAULT
Expand Down Expand Up @@ -753,7 +753,7 @@ def parse_pseudo_open(
if name == ':not':
flags |= FLG_NOT
elif name == ':has':
flags |= FLG_RELATIVE | FLG_FORGIVE
flags |= FLG_RELATIVE
elif name in (':where', ':is'):
flags |= FLG_FORGIVE

Expand All @@ -777,11 +777,6 @@ def parse_has_combinator(
if not combinator:
combinator = WS_COMBINATOR
if combinator == COMMA_COMBINATOR:
if not has_selector:
# If we've not captured any selector parts, the comma is either at the beginning of the pattern
# or following another comma, both of which are unexpected. But shouldn't fail the pseudo-class.
sel.no_match = True

sel.rel_type = rel_type
selectors[-1].relations.append(sel)
rel_type = ":" + WS_COMBINATOR
Expand Down Expand Up @@ -1070,22 +1065,12 @@ def parse_selectors(
selectors.append(sel)

# Forgive empty slots in pseudo-classes that have lists (and are forgiving)
elif is_forgive:
if is_relative:
# Handle relative selectors pseudo-classes with empty slots like `:has()`
if selectors and selectors[-1].rel_type is None and rel_type == ': ':
sel.rel_type = rel_type
sel.no_match = True
selectors[-1].relations.append(sel)
has_selector = True
else:
# Handle normal pseudo-classes with empty slots
if not selectors or not relations:
# Others like `:is()` etc.
sel.no_match = True
del relations[:]
selectors.append(sel)
has_selector = True
elif is_forgive and (not selectors or not relations):
# Handle normal pseudo-classes with empty slots like `:is()` etc.
sel.no_match = True
del relations[:]
selectors.append(sel)
has_selector = True

if not has_selector:
# We will always need to finish a selector when `:has()` is used as it leads with combining.
Expand Down
35 changes: 5 additions & 30 deletions tests/test_level4/test_has.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,45 +129,20 @@ def test_has_nested_pseudo(self):
flags=util.HTML
)

def test_has_empty(self):
"""Test has with empty slot due to multiple commas."""
def test_has_no_match(self):
"""Test has with a non-matching selector."""

self.assert_selector(
self.MARKUP2,
'div:has()',
'div:has(:paused)',
[],
flags=util.HTML
)

def test_has_multi_commas(self):
def test_has_empty(self):
"""Test has with empty slot due to multiple commas."""

self.assert_selector(
self.MARKUP2,
'div:has(> .bbbb, .ffff, , .jjjj)',
['0', '4', '8'],
flags=util.HTML
)

def test_has_leading_commas(self):
"""Test has with empty slot due to leading commas."""

self.assert_selector(
self.MARKUP2,
'div:has(, > .bbbb, .ffff, .jjjj)',
['0', '4', '8'],
flags=util.HTML
)

def test_has_trailing_commas(self):
"""Test has with empty slot due to trailing commas."""

self.assert_selector(
self.MARKUP2,
'div:has(> .bbbb, .ffff, .jjjj, )',
['0', '4', '8'],
flags=util.HTML
)
self.assert_raises('div:has()', SelectorSyntaxError)

def test_invalid_incomplete_has(self):
"""Test `:has()` fails with just a combinator."""
Expand Down
10 changes: 0 additions & 10 deletions tests/test_level4/test_nth_child.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,3 @@ def test_nth_child_of_s_complex(self):
['2', '6', '10'],
flags=util.HTML
)

def test_nth_child_forgive(self):
"""Test that `nth` child forgives bad commas."""

self.assert_selector(
self.MARKUP,
":nth-child(2n + 1 OF p.test,, span.test,)",
['2', '6', '10'],
flags=util.HTML
)
27 changes: 0 additions & 27 deletions tests/test_level4/test_nth_last_child.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,30 +31,3 @@ def test_nth_child_of_s_complex(self):
['1', '3', '5', '7', '9', '11'],
flags=util.HTML
)

def test_nth_last_child_forgive(self):
"""Test `nth` last child forgives irregular commas."""

markup = """
<body>
<p id="0"></p>
<p id="1"></p>
<span id="2"></span>
<span id="3"></span>
<span id="4"></span>
<span id="5"></span>
<span id="6"></span>
<p id="7"></p>
<p id="8"></p>
<p id="9"></p>
<p id="10"></p>
<span id="11"></span>
</body>
"""

self.assert_selector(
markup,
":nth-last-child(2n + 1 of ,p[id],, span[id],)",
['1', '3', '5', '7', '9', '11'],
flags=util.HTML
)

0 comments on commit 02ca817

Please sign in to comment.