Positional Selectors: :first and :last
Relevant source files
The following files were used as context for generating this wiki page:
cheerio-select acts as a specialized CSS selector engine that extends the standard capabilities of css-select by adding support for jQuery-style positional pseudo-selectors. These selectors, most notably :first and :last, allow developers to target elements based on their specific index or relative position within a set of matched elements, rather than just their attributes or tree relationships.
The engine serves as a thin wrapper that identifies these "positional" tokens and handles them through a custom filtering pipeline. While :first and :last are common, the system also supports more complex index-based filtering including :eq(index), :gt(index), and :lt(index).
Sources: README.md:1-12, src/index.ts:1-25
Architectural Overview
The selection process in cheerio-select involves a two-stage approach: grouping and recursive filtering. When a selector string is parsed, the engine separates standard CSS selectors from jQuery-style positional filters.
Selector Grouping
The engine utilizes a helper function, groupSelectors, to split a list of parsed selectors into "plain" selectors (standard CSS) and "filtered" selectors (those containing positional pseudos). Plain selectors are passed directly to the underlying css-select compiler, while filtered selectors trigger a specialized findFilterElements logic path.
Sources: src/index.ts:60-66, src/helpers.spec.ts:7-13
Positional Processing Flow
The following diagram illustrates how the engine handles a selector containing positional elements like :first.
The logic ensures that positional filters are applied to the result set produced by the selector segments preceding them.
Sources: src/index.ts:58-75, src/index.ts:251-275
Logic and Implementation
filterByPosition Function
The core logic for extracting the first or last element resides in the filterByPosition function. This function takes a subset of elements and applies the specific logic dictated by the pseudo-selector name.
:first: Effectively returns the provided elements as-is because the result set is already limited to 1 during the initial collection phase viagetLimit.:last: Returns an array containing only the element atelements.length - 1.
| Selector | Logic Description | Internal Implementation |
|---|---|---|
:first | Selects the first element in the current set. | Returns current set (limited by index 0). |
:last | Selects the final element in the current set. | [elements[elements.length - 1]] |
:eq(n) | Selects element at index n. | Uses index access with support for negative offsets. |
:even | Selects elements with even indices. | elements.filter((_, i) => i % 2 === 0) |
Sources: src/index.ts:77-113, README.md:14-22
Recursive Querying
cheerio-select supports complex selectors where positionals are in the middle of the string, such as div:first span. In these cases, the engine:
- Finds all
divelements. - Filters to the first
div. - Resets the context/root to that specific element.
- Continues the search for
spanwithin that new context.
Sources: src/index.ts:277-310, test/advanced-selectors.spec.ts:47-51
Key Functionality and Data Structures
Technical Specifications
The system relies on specific interfaces to manage the selector tokens provided by css-what.
CheerioSelector: Represents a positional pseudo-selector token (typePseudo) with a name and optional data (like the index in:eq(5)).findFilterElements: The primary recursive engine for navigating through a selector array that contains positionals.
Sources: src/index.ts:241-275, src/positionals.ts
Index Handling
For :first, the engine optimizes by setting a limit of 1 during the element collection phase. This prevents unnecessary traversal of the DOM once the first matching element is found. For :last, the engine must collect all matching candidates first to identify which one is final.
Sources: src/index.ts:245-258, src/index.spec.ts:107-117
Usage and Testing Examples
Testing fixtures demonstrate that these selectors are order-dependent and context-sensitive.
- Combined Selectors:
h2:first, h1:firstselects the first of each tag type within the search scope. - Contextual Filtering: When using
filter(":first", ps), the engine correctly identifies the first element within a pre-existing array of nodes, regardless of their original position in the document.
Sources: test/sizzle.spec.ts:114-115, src/index.spec.ts:46-51
// Example from test/advanced-selectors.spec.ts
const dom = parseDocument("<div>foo</div><div>bar</div>");
const result = select("div:first", dom); // Returns <div>foo</div>
Sources: test/advanced-selectors.spec.ts:14-18
Summary
Positional selectors like :first and :last provide a powerful mechanism for narrowing down query results based on their index. By wrapping css-select and implementing a recursive filtering strategy in src/index.ts, cheerio-select enables jQuery-compatible DOM manipulation within the Cheerio ecosystem. The architecture prioritizes performance by applying limits during collection whenever possible, particularly for the :first selector.