From 58e090a1f339f3793647095d9e905ec649b97204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20B=C3=B6hm?= <188768+fb55@users.noreply.github.com> Date: Mon, 7 Jun 2021 16:38:56 +0100 Subject: [PATCH] refactor(traversing): Wrap shared behavior (#1909) Co-authored-by: 5saviahv <49443574+5saviahv@users.noreply.github.com> --- src/__fixtures__/fixtures.ts | 26 ++ src/api/traversing.spec.ts | 118 ++++++-- src/api/traversing.ts | 548 ++++++++++++++--------------------- 3 files changed, 330 insertions(+), 362 deletions(-) diff --git a/src/__fixtures__/fixtures.ts b/src/__fixtures__/fixtures.ts index 749d15ae12..f1b7c147ad 100644 --- a/src/__fixtures__/fixtures.ts +++ b/src/__fixtures__/fixtures.ts @@ -47,6 +47,32 @@ export const drinks = [ export const food = [''].join(''); +export const eleven = ` + + + + + + + + + +`; + export const inputs = [ '', '', diff --git a/src/api/traversing.spec.ts b/src/api/traversing.spec.ts index d8e5ded3ea..c6959179dc 100644 --- a/src/api/traversing.spec.ts +++ b/src/api/traversing.spec.ts @@ -5,6 +5,7 @@ import { Node, Element, Text, isText } from 'domhandler'; import { food, fruits, + eleven, drinks, text, forms, @@ -214,6 +215,21 @@ describe('$(...)', () => { expect($('.apple, .orange', food).next()).toHaveLength(2); }); + it('() : should return elements in order', () => { + const result = cheerio.load(eleven)('.red').next(); + expect(result).toHaveLength(2); + expect(result.eq(0).text()).toBe('Six'); + expect(result.eq(1).text()).toBe('Ten'); + }); + + it('should reject elements that violate the filter', () => { + expect($('.apple').next('.non-existent')).toHaveLength(0); + }); + + it('should accept elements that satisify the filter', () => { + expect($('.apple').next('.orange')).toHaveLength(1); + }); + describe('(selector) :', () => { it('should reject elements that violate the filter', () => { expect($('.apple').next('.non-existent')).toHaveLength(0); @@ -250,6 +266,11 @@ describe('$(...)', () => { expect(elems.nextAll()).toHaveLength(2); }); + it('() : should not contain text elements', () => { + const elems = $('.apple', fruits.replace(/>\n<')); + expect(elems.nextAll()).toHaveLength(2); + }); + describe('(selector) :', () => { it('should filter according to the provided selector', () => { expect($('.apple').nextAll('.pear')).toHaveLength(1); @@ -359,6 +380,25 @@ describe('$(...)', () => { expect($('.orange, .pear', food).prev()).toHaveLength(2); }); + it('() : should maintain elements order', () => { + const sel = cheerio.load(eleven)('.sel'); + expect(sel).toHaveLength(3); + expect(sel.eq(0).text()).toBe('Three'); + expect(sel.eq(1).text()).toBe('Nine'); + expect(sel.eq(2).text()).toBe('Eleven'); + + // Swap last elements + const el = sel[2]; + sel[2] = sel[1]; + sel[1] = el; + + const result = sel.prev(); + expect(result).toHaveLength(3); + expect(result.eq(0).text()).toBe('Two'); + expect(result.eq(1).text()).toBe('Ten'); + expect(result.eq(2).text()).toBe('Eight'); + }); + describe('(selector) :', () => { it('should reject elements that violate the filter', () => { expect($('.orange').prev('.non-existent')).toHaveLength(0); @@ -367,6 +407,14 @@ describe('$(...)', () => { it('should accept elements that satisify the filter', () => { expect($('.orange').prev('.apple')).toHaveLength(1); }); + + it('(selector) : should reject elements that violate the filter', () => { + expect($('.orange').prev('.non-existent')).toHaveLength(0); + }); + + it('(selector) : should accept elements that satisify the filter', () => { + expect($('.orange').prev('.apple')).toHaveLength(1); + }); }); }); @@ -378,6 +426,11 @@ describe('$(...)', () => { expect(elems[1].attribs.class).toBe('apple'); }); + it('() : should not contain text elements', () => { + const elems = $('.pear', fruits.replace(/>\n<')); + expect(elems.prevAll()).toHaveLength(2); + }); + it('(no prev) : should return empty for first child', () => { expect($('.apple').prevAll()).toHaveLength(0); }); @@ -508,6 +561,35 @@ describe('$(...)', () => { it('(selector) : does not consider the contents of siblings when filtering (GH-374)', () => { expect($('#fruits', food).siblings('li')).toHaveLength(0); }); + + it('() : when two elements are siblings to each other they have to be included', () => { + const result = cheerio.load(eleven)('.sel').siblings(); + expect(result).toHaveLength(7); + expect(result.eq(0).text()).toBe('One'); + expect(result.eq(1).text()).toBe('Two'); + expect(result.eq(2).text()).toBe('Four'); + expect(result.eq(3).text()).toBe('Eight'); + expect(result.eq(4).text()).toBe('Nine'); + expect(result.eq(5).text()).toBe('Ten'); + expect(result.eq(6).text()).toBe('Eleven'); + }); + + it('(selector) : when two elements are siblings to each other they have to be included', () => { + const result = cheerio.load(eleven)('.sel').siblings('.red'); + expect(result).toHaveLength(2); + expect(result.eq(0).text()).toBe('Four'); + expect(result.eq(1).text()).toBe('Nine'); + }); + + it('(cheerio) : test filtering with cheerio object', () => { + const doc = cheerio.load(eleven); + const result = doc('.sel').siblings(doc(':not([class])')); + expect(result).toHaveLength(4); + expect(result.eq(0).text()).toBe('One'); + expect(result.eq(1).text()).toBe('Two'); + expect(result.eq(2).text()).toBe('Eight'); + expect(result.eq(3).text()).toBe('Ten'); + }); }); describe('.parents', () => { @@ -554,10 +636,10 @@ describe('$(...)', () => { expect($parents).toHaveLength(5); expect($parents[0]).toBe($('#vegetables')[0]); - expect($parents[1]).toBe($('#food')[0]); - expect($parents[2]).toBe($('body')[0]); - expect($parents[3]).toBe($('html')[0]); - expect($parents[4]).toBe($('#fruits')[0]); + expect($parents[1]).toBe($('#fruits')[0]); + expect($parents[2]).toBe($('#food')[0]); + expect($parents[3]).toBe($('body')[0]); + expect($parents[4]).toBe($('html')[0]); }); }); @@ -578,11 +660,11 @@ describe('$(...)', () => { it('() : should get all of the parents in reversed order, omitting duplicates', () => { const result = $('.apple, .sweetcorn').parentsUntil(); expect(result).toHaveLength(5); - expect(result[0].attribs.id).toBe('vegetables'); - expect(result[1].attribs.id).toBe('food'); - expect(result[2].tagName).toBe('body'); - expect(result[3].tagName).toBe('html'); - expect(result[4].attribs.id).toBe('fruits'); + expect(result[0]).toBe($('#vegetables')[0]); + expect(result[1]).toBe($('#fruits')[0]); + expect(result[2]).toBe($('#food')[0]); + expect(result[3]).toBe($('body')[0]); + expect(result[4]).toBe($('html')[0]); }); it('(selector) : should get all of the parents until selector', () => { @@ -818,12 +900,6 @@ describe('$(...)', () => { }); describe('.filter', () => { - it('should throw if it cannot construct an object', () => { - // @ts-expect-error Calling `filter` without a cheerio instance. - expect(() => $('').filter.call([], '')).toThrow( - 'Not able to create a Cheerio instance.' - ); - }); it('(selector) : should reduce the set of matched elements to those that match the selector', () => { const pear = $('li').filter('.pear').text(); expect(pear).toBe('Pear'); @@ -869,12 +945,6 @@ describe('$(...)', () => { }); describe('.not', () => { - it('should throw if it cannot construct an object', () => { - expect(() => $('').not.call([], '')).toThrow( - 'Not able to create a Cheerio instance.' - ); - }); - it('(selector) : should reduce the set of matched elements to those that do not match the selector', () => { const $fruits = $('li'); @@ -926,12 +996,6 @@ describe('$(...)', () => { expect($notOrange[0]).toBe($fruits[0]); expect($notOrange[1]).toBe($fruits[2]); }); - - it('(arr, str, $) : should take cheerio instance as last arg', () => { - const $fruits = $('#fruits'); - const lis = $fruits.not.call($fruits.toArray(), 'li', $fruits); - expect(lis).toHaveLength(1); - }); }); describe('.has', () => { diff --git a/src/api/traversing.ts b/src/api/traversing.ts index 5bca42191c..22afee5944 100644 --- a/src/api/traversing.ts +++ b/src/api/traversing.ts @@ -4,11 +4,12 @@ * @module cheerio/traversing */ -import { Node, Element, hasChildren } from 'domhandler'; +import { Node, Element, hasChildren, isDocument } from 'domhandler'; import type { Cheerio } from '../cheerio'; import * as select from 'cheerio-select'; import { domEach, isTag, isCheerio } from '../utils'; import { contains } from '../static'; +import { InternalOptions } from '../options'; import { DomUtils } from 'htmlparser2'; import type { FilterFunction, AcceptedFilters } from '../types'; const { uniqueSort } = DomUtils; @@ -67,6 +68,122 @@ export function find( return this._make(select.select(selectorOrHaystack, elems, options)); } +/** + * Creates a matcher, using a particular mapping function. Matchers provide a + * function that finds elements using a generating function, supporting filtering. + * + * @private + * @param matchMap - Mapping function. + * @returns - Function for wrapping generating functions. + */ +function _getMatcher

( + matchMap: (fn: (elem: Node) => P, elems: Cheerio) => Element[] +) { + return function ( + fn: (elem: Node) => P, + ...postFns: ((elems: Element[]) => Element[])[] + ) { + return function ( + this: Cheerio, + selector?: AcceptedFilters + ): Cheerio { + let matched: Element[] = matchMap(fn, this); + + if (selector) { + matched = filterArray(matched, selector, this.options); + } + + return this._make( + // Post processing is only necessary if there is more than one element. + this.length > 1 && matched.length > 1 + ? postFns.reduce((elems, fn) => fn(elems), matched) + : matched + ); + }; + }; +} + +/** Matcher that adds multiple elements for each entry in the input. */ +const _matcher = _getMatcher((fn: (elem: Node) => Element[], elems) => { + const ret: Element[][] = []; + + for (let i = 0; i < elems.length; i++) { + const value = fn(elems[i]); + ret.push(value); + } + + return new Array().concat(...ret); +}); + +/** Matcher that adds at most one element for each entry in the input. */ +const _singleMatcher = _getMatcher( + (fn: (elem: Node) => Element | null, elems) => { + const ret: Element[] = []; + + for (let i = 0; i < elems.length; i++) { + const value = fn(elems[i]); + if (value !== null) { + ret.push(value); + } + } + return ret; + } +); + +/** + * Matcher that supports traversing until a condition is met. + * + * @returns A function usable for `*Until` methods. + */ +function _matchUntil( + nextElem: (elem: Node) => Element | null, + ...postFns: ((elems: Element[]) => Element[])[] +) { + // We use a variable here that is used from within the matcher. + let matches: ((el: Element, i: number) => boolean) | null = null; + + const innerMatcher = _getMatcher( + (nextElem: (elem: Node) => Element | null, elems) => { + const matched: Element[] = []; + + domEach(elems, (elem) => { + for (let next; (next = nextElem(elem)); elem = next) { + // FIXME: `matched` might contain duplicates here and the index is too large. + if (matches?.(next, matched.length)) break; + matched.push(next); + } + }); + + return matched; + } + )(nextElem, ...postFns); + + return function ( + this: Cheerio, + selector?: AcceptedFilters | null, + filterSelector?: AcceptedFilters + ): Cheerio { + // Override `matches` variable with the new target. + matches = + typeof selector === 'string' + ? (elem: Element) => select.is(elem, selector, this.options) + : selector + ? getFilterFn(selector) + : null; + + const ret = innerMatcher.call(this, filterSelector); + + // Set `matches` to `null`, so we don't waste memory. + matches = null; + + return ret; + }; +} + +function _removeDuplicates(elems: T[]): T[] { + return Array.from(new Set(elems)); +} + /** * Get the parent of each element in the current set of matched elements, * optionally filtered by a selector. @@ -83,25 +200,10 @@ export function find( * @returns The parents. * @see {@link https://api.jquery.com/parent/} */ -export function parent( - this: Cheerio, - selector?: AcceptedFilters -): Cheerio { - const set: Element[] = []; - - domEach(this, (elem) => { - const parentElem = elem.parent; - if ( - parentElem && - parentElem.type !== 'root' && - !set.includes(parentElem as Element) - ) { - set.push(parentElem as Element); - } - }); - - return selector ? filter.call(set, selector, this) : this._make(set); -} +export const parent = _singleMatcher( + ({ parent }) => (parent && !isDocument(parent) ? (parent as Element) : null), + _removeDuplicates +); /** * Get a set of parents filtered by `selector` of each element in the current @@ -121,30 +223,18 @@ export function parent( * @returns The parents. * @see {@link https://api.jquery.com/parents/} */ -export function parents( - this: Cheerio, - selector?: AcceptedFilters -): Cheerio { - const parentNodes: Element[] = []; - - /* - * When multiple DOM elements are in the original set, the resulting set will - * be in *reverse* order of the original elements as well, with duplicates - * removed. - */ - this.get() - .reverse() - .forEach((elem) => - traverseParents(this, elem.parent, selector, Infinity).forEach((node) => { - // We know these must be `Element`s, as we filter out root nodes. - if (!parentNodes.includes(node as Element)) { - parentNodes.push(node as Element); - } - }) - ); - - return this._make(parentNodes); -} +export const parents = _matcher( + (elem) => { + const matched = []; + while (elem.parent && !isDocument(elem.parent)) { + matched.push(elem.parent as Element); + elem = elem.parent; + } + return matched; + }, + uniqueSort, + (elems) => elems.reverse() +); /** * Get the ancestors of each element in the current set of matched elements, up @@ -159,56 +249,15 @@ export function parents( * ``` * * @param selector - Selector for element to stop at. - * @param filterBy - Optional filter for parents. + * @param filterSelector - Optional filter for parents. * @returns The parents. * @see {@link https://api.jquery.com/parentsUntil/} */ -export function parentsUntil( - this: Cheerio, - selector?: string | Node | Cheerio, - filterBy?: AcceptedFilters -): Cheerio { - const parentNodes: Element[] = []; - let untilNode: Node | undefined; - let untilNodes: Node[] | undefined; - - if (typeof selector === 'string') { - untilNodes = this.parents(selector).toArray(); - } else if (selector && isCheerio(selector)) { - untilNodes = selector.toArray(); - } else if (selector) { - untilNode = selector; - } - - /* - * When multiple DOM elements are in the original set, the resulting set will - * be in *reverse* order of the original elements as well, with duplicates - * removed. - */ - - this.toArray() - .reverse() - .forEach((elem: Node) => { - while (elem.parent) { - elem = elem.parent; - if ( - (untilNode && elem !== untilNode) || - (untilNodes && !untilNodes.includes(elem)) || - (!untilNode && !untilNodes) - ) { - if (isTag(elem) && !parentNodes.includes(elem)) { - parentNodes.push(elem); - } - } else { - break; - } - } - }, this); - - return filterBy - ? filter.call(parentNodes, filterBy, this) - : this._make(parentNodes); -} +export const parentsUntil = _matchUntil( + ({ parent }) => (parent && !isDocument(parent) ? (parent as Element) : null), + uniqueSort, + (elems) => elems.reverse() +); /** * For each element in the set, get the first element that matches the selector @@ -237,7 +286,7 @@ export function parentsUntil( */ export function closest( this: Cheerio, - selector?: AcceptedFilters + selector?: AcceptedFilters ): Cheerio { const set: Node[] = []; @@ -245,12 +294,16 @@ export function closest( return this._make(set); } - domEach(this, (elem) => { - const closestElem = traverseParents(this, elem, selector, 1)[0]; - - // Do not add duplicate elements to the set - if (closestElem && !set.includes(closestElem)) { - set.push(closestElem); + domEach(this, (elem: Node | null) => { + while (elem && elem.type !== 'root') { + if (!selector || filterArray([elem], selector, this.options).length) { + // Do not add duplicate elements to the set + if (elem && !set.includes(elem)) { + set.push(elem); + } + break; + } + elem = elem.parent; } }); @@ -272,24 +325,7 @@ export function closest( * @returns The next nodes. * @see {@link https://api.jquery.com/next/} */ -export function next( - this: Cheerio, - selector?: AcceptedFilters -): Cheerio { - const elems: Element[] = []; - - domEach(this, (elem) => { - while (elem.next) { - elem = elem.next; - if (isTag(elem)) { - elems.push(elem); - return; - } - } - }); - - return selector ? filter.call(elems, selector, this) : this._make(elems); -} +export const next = _singleMatcher((elem) => DomUtils.nextElementSibling(elem)); /** * Gets all the following siblings of the first selected element, optionally @@ -309,23 +345,14 @@ export function next( * @returns The next nodes. * @see {@link https://api.jquery.com/nextAll/} */ -export function nextAll( - this: Cheerio, - selector?: AcceptedFilters -): Cheerio { - const elems: Element[] = []; - - domEach(this, (elem: Node) => { - while (elem.next) { - elem = elem.next; - if (isTag(elem) && !elems.includes(elem)) { - elems.push(elem); - } - } - }); - - return selector ? filter.call(elems, selector, this) : this._make(elems); -} +export const nextAll = _matcher((elem) => { + const matched = []; + while (elem.next) { + elem = elem.next; + if (isTag(elem)) matched.push(elem); + } + return matched; +}, _removeDuplicates); /** * Gets all the following siblings up to but not including the element matched @@ -344,44 +371,10 @@ export function nextAll( * @returns The next nodes. * @see {@link https://api.jquery.com/nextUntil/} */ -export function nextUntil( - this: Cheerio, - selector?: string | Cheerio | Node | null, - filterSelector?: AcceptedFilters -): Cheerio { - const elems: Element[] = []; - let untilNode: Node | undefined; - let untilNodes: Node[] | undefined; - - if (typeof selector === 'string') { - untilNodes = this.nextAll(selector).toArray(); - } else if (selector && isCheerio(selector)) { - untilNodes = selector.get(); - } else if (selector) { - untilNode = selector; - } - - domEach(this, (elem) => { - while (elem.next) { - elem = elem.next; - if ( - (untilNode && elem !== untilNode) || - (untilNodes && !untilNodes.includes(elem)) || - (!untilNode && !untilNodes) - ) { - if (isTag(elem) && !elems.includes(elem)) { - elems.push(elem); - } - } else { - break; - } - } - }); - - return filterSelector - ? filter.call(elems, filterSelector, this) - : this._make(elems); -} +export const nextUntil = _matchUntil( + (el) => DomUtils.nextElementSibling(el), + _removeDuplicates +); /** * Gets the previous sibling of the first selected element optionally filtered @@ -399,24 +392,7 @@ export function nextUntil( * @returns The previous nodes. * @see {@link https://api.jquery.com/prev/} */ -export function prev( - this: Cheerio, - selector?: AcceptedFilters -): Cheerio { - const elems: Element[] = []; - - domEach(this, (elem: Node) => { - while (elem.prev) { - elem = elem.prev; - if (isTag(elem)) { - elems.push(elem); - return; - } - } - }); - - return selector ? filter.call(elems, selector, this) : this._make(elems); -} +export const prev = _singleMatcher((elem) => DomUtils.prevElementSibling(elem)); /** * Gets all the preceding siblings of the first selected element, optionally @@ -437,23 +413,14 @@ export function prev( * @returns The previous nodes. * @see {@link https://api.jquery.com/prevAll/} */ -export function prevAll( - this: Cheerio, - selector?: AcceptedFilters -): Cheerio { - const elems: Element[] = []; - - domEach(this, (elem) => { - while (elem.prev) { - elem = elem.prev; - if (isTag(elem) && !elems.includes(elem)) { - elems.push(elem); - } - } - }); - - return selector ? filter.call(elems, selector, this) : this._make(elems); -} +export const prevAll = _matcher((elem) => { + const matched = []; + while (elem.prev) { + elem = elem.prev; + if (isTag(elem)) matched.push(elem); + } + return matched; +}, _removeDuplicates); /** * Gets all the preceding siblings up to but not including the element matched @@ -472,44 +439,10 @@ export function prevAll( * @returns The previous nodes. * @see {@link https://api.jquery.com/prevUntil/} */ -export function prevUntil( - this: Cheerio, - selector?: string | Cheerio | Node | null, - filterSelector?: AcceptedFilters -): Cheerio { - const elems: Element[] = []; - let untilNode: Node | undefined; - let untilNodes: Node[] | undefined; - - if (typeof selector === 'string') { - untilNodes = this.prevAll(selector).toArray(); - } else if (selector && isCheerio(selector)) { - untilNodes = selector.get(); - } else if (selector) { - untilNode = selector; - } - - domEach(this, (elem) => { - while (elem.prev) { - elem = elem.prev; - if ( - (untilNode && elem !== untilNode) || - (untilNodes && !untilNodes.includes(elem)) || - (!untilNode && !untilNodes) - ) { - if (isTag(elem) && !elems.includes(elem)) { - elems.push(elem); - } - } else { - break; - } - } - }); - - return filterSelector - ? filter.call(elems, filterSelector, this) - : this._make(elems); -} +export const prevUntil = _matchUntil( + (el) => DomUtils.prevElementSibling(el), + _removeDuplicates +); /** * Get the siblings of each element (excluding the element) in the set of @@ -530,21 +463,13 @@ export function prevUntil( * @returns The siblings. * @see {@link https://api.jquery.com/siblings/} */ -export function siblings( - this: Cheerio, - selector?: AcceptedFilters -): Cheerio { - // TODO Still get siblings if `parent` is null; see DomUtils' `getSiblings`. - const parent = this.parent(); - - const elems = parent - .children() - .toArray() - // TODO: This removes all elements in the selection. Note that they could be added here, if siblings are part of the selection. - .filter((elem: Node) => !this.is(elem)); - - return selector ? filter.call(elems, selector, this) : this._make(elems); -} +export const siblings = _matcher( + (elem) => + DomUtils.getSiblings(elem).filter( + (el): el is Element => isTag(el) && el !== elem + ), + uniqueSort +); /** * Gets the children of the first selected element. @@ -564,20 +489,10 @@ export function siblings( * @returns The children. * @see {@link https://api.jquery.com/children/} */ -export function children( - this: Cheerio, - selector?: AcceptedFilters -): Cheerio { - const elems = this.toArray().reduce( - (newElems, elem) => - hasChildren(elem) - ? newElems.concat(elem.children.filter(isTag)) - : newElems, - [] - ); - - return selector ? filter.call(elems, selector, this) : this._make(elems); -} +export const children = _matcher( + (elem) => DomUtils.getChildren(elem).filter(isTag), + _removeDuplicates +); /** * Gets the children of each element in the set of matched elements, including @@ -679,6 +594,12 @@ export function map( return this._make(elems); } +/** + * Creates a function to test if a filter is matched. + * + * @param match - A filter. + * @returns A function that determines if a filter has been matched. + */ function getFilterFn( match: FilterFunction | Cheerio | T ): (el: T, i: number) => boolean { @@ -686,7 +607,7 @@ function getFilterFn( return (el, i) => (match as FilterFunction).call(el, i, el); } if (isCheerio(match)) { - return (el) => match.is(el); + return (el) => Array.prototype.includes.call(match, el); } return function (el) { return match === el; @@ -760,41 +681,21 @@ export function filter>( this: Cheerio, match: S ): Cheerio; -/** - * Internal `filter` variant used by other functions to filter their elements. - * - * @private - * @param match - Value to look for, following the rules above. - * @param container - The container that is used to create the resulting Cheerio instance. - * @returns The filtered collection. - * @see {@link https://api.jquery.com/filter/} - */ -export function filter( - this: T[], - match: AcceptedFilters, - container: Cheerio -): Cheerio; export function filter( - this: Cheerio | T[], - match: AcceptedFilters, - container = this + this: Cheerio, + match: AcceptedFilters ): Cheerio { - if (!isCheerio(container)) { - throw new Error('Not able to create a Cheerio instance.'); - } - - const nodes = isCheerio(this) ? this.toArray() : this; - - const result = - typeof match === 'string' - ? select.filter( - match, - (nodes as unknown as Node[]).filter(isTag), - container.options - ) - : nodes.filter(getFilterFn(match)); + return this._make(filterArray(this.toArray(), match, this.options)); +} - return container._make(result); +export function filterArray( + nodes: T[], + match: AcceptedFilters, + options: InternalOptions +): Element[] | T[] { + return typeof match === 'string' + ? select.filter(match, (nodes as unknown as Node[]).filter(isTag), options) + : nodes.filter(getFilterFn(match)); } /** @@ -859,28 +760,21 @@ export function is( * @see {@link https://api.jquery.com/not/} */ export function not( - this: Cheerio | T[], - match: AcceptedFilters, - container = this + this: Cheerio, + match: AcceptedFilters ): Cheerio { - if (!isCheerio(container)) { - throw new Error('Not able to create a Cheerio instance.'); - } - - let nodes = isCheerio(this) ? this.toArray() : this; + let nodes = this.toArray(); if (typeof match === 'string') { const elements = (nodes as Node[]).filter(isTag); - const matches = new Set( - select.filter(match, elements, container.options) - ); + const matches = new Set(select.filter(match, elements, this.options)); nodes = nodes.filter((el) => !matches.has(el)); } else { const filterFn = getFilterFn(match); nodes = nodes.filter((el, i) => !filterFn(el, i)); } - return container._make(nodes); + return this._make(nodes); } /** @@ -1076,7 +970,7 @@ export function index( : selectorOrNeedle; } - return $haystack.get().indexOf(needle); + return Array.prototype.indexOf.call($haystack, needle); } /** @@ -1109,22 +1003,6 @@ export function slice( return this._make(Array.prototype.slice.call(this, start, end)); } -function traverseParents( - self: Cheerio, - elem: Node | null, - selector: AcceptedFilters | undefined, - limit: number -): Node[] { - const elems: Node[] = []; - while (elem && elems.length < limit && elem.type !== 'root') { - if (!selector || filter.call([elem], selector, self).length) { - elems.push(elem); - } - elem = elem.parent; - } - return elems; -} - /** * End the most recent filtering operation in the current chain and return the * set of matched elements to its previous state.