Skip to content

is()/:where() with complex selectors containing :nth-child()` fails to match #165

Closed

Description

@jackboike

Issue

querySelector with :is() fails when the selector list inside :is() contains a complex selector with both a child combinator (>) and :nth-child().

Reproduction:

const { JSDOM } = require("jsdom");
const dom = new JSDOM(`<table><thead><tr><th data-column-index="1"><div role="button">Sort</div></th></tr></thead></table>`);
const thead = dom.window.document.querySelector("thead");

// Works
thead.querySelector(':is(th[data-column-index="1"]) [role=button]'); // ✓

// Works
thead.querySelector(':is(tr > th) [role=button]'); // ✓

// FAILS
thead.querySelector(':is(th[data-column-index="1"], tr:not([data-group-level]) > *:nth-child(1)) [role=button]'); // ✗ returns null

// Works: equivalent comma-separated selector
thead.querySelector('th[data-column-index="1"] [role=button], tr:not([data-group-level]) > *:nth-child(1) [role=button]'); // ✓

Root cause: The logicalsel regex on line 81 uses [^()]*|.* where the greedy .* consumes past the closing ) of :is() when content has nested parens from :not() or :nth-child().

Suggested fix: Replace '[^()]*|.*' with '[^()]*(?:\\([^()]*(?:\\([^()]*\\)[^()]*)*\\)[^()]*)*' for balanced parenthesis matching up to 2 levels.

Environment: nwsapi 2.2.24, jsdom 20-26, Node 22.x

Activity

  1. jdalton commented on Sep 5, 2026

    @jdalton
    Collaborator

    I cannot reproduce this on master at 2e9498f, which is 2.2.27: all three selectors from the report return the div[role=button], including the one marked as failing.

    What I ran, and the variations I tried around it
    const thead = document.querySelector('thead');
    NW.first(':is(th[data-column-index="1"], tr:not([data-group-level]) > *:nth-child(1)) [role=button]', thead);

    That finds the div on master. So do three narrower shapes I tried to isolate the reported one: :is(tr:not([data-group-level]) > *:nth-child(1)) [role=button], :is(tr > *:nth-child(1)) [role=button], and :is(:not([data-x]) > *:nth-child(1)) [role=button].

    So whatever broke it looks to have been fixed between the version you hit and current master.

    Is it still failing for you on 2.2.27? If it is, the version and the surrounding markup would pin it down, since the shape alone passes here.

  2. jdalton commented on Sep 6, 2026

    @jdalton
    Collaborator

    The reported selector now returns the expected button on current master, for both :is() and :where(). I verified both forms during the merge audit. Nested logical arguments are now handled by the balanced scanner from merged #179. Closing this report as addressed in source; this does not indicate that a new npm release has been published.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions