@depup/cheerio 1.2.0-depup.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +36 -0
  3. package/Readme.md +229 -0
  4. package/changes.json +30 -0
  5. package/dist/browser/api/attributes.d.ts +385 -0
  6. package/dist/browser/api/attributes.d.ts.map +1 -0
  7. package/dist/browser/api/attributes.js +636 -0
  8. package/dist/browser/api/attributes.js.map +1 -0
  9. package/dist/browser/api/css.d.ts +42 -0
  10. package/dist/browser/api/css.d.ts.map +1 -0
  11. package/dist/browser/api/css.js +116 -0
  12. package/dist/browser/api/css.js.map +1 -0
  13. package/dist/browser/api/extract.d.ts +27 -0
  14. package/dist/browser/api/extract.d.ts.map +1 -0
  15. package/dist/browser/api/extract.js +42 -0
  16. package/dist/browser/api/extract.js.map +1 -0
  17. package/dist/browser/api/forms.d.ts +36 -0
  18. package/dist/browser/api/forms.d.ts.map +1 -0
  19. package/dist/browser/api/forms.js +81 -0
  20. package/dist/browser/api/forms.js.map +1 -0
  21. package/dist/browser/api/manipulation.d.ts +528 -0
  22. package/dist/browser/api/manipulation.d.ts.map +1 -0
  23. package/dist/browser/api/manipulation.js +831 -0
  24. package/dist/browser/api/manipulation.js.map +1 -0
  25. package/dist/browser/api/traversing.d.ts +657 -0
  26. package/dist/browser/api/traversing.d.ts.map +1 -0
  27. package/dist/browser/api/traversing.js +857 -0
  28. package/dist/browser/api/traversing.js.map +1 -0
  29. package/dist/browser/cheerio.d.ts +85 -0
  30. package/dist/browser/cheerio.d.ts.map +1 -0
  31. package/dist/browser/cheerio.js +58 -0
  32. package/dist/browser/cheerio.js.map +1 -0
  33. package/dist/browser/index-browser.d.mts.map +1 -0
  34. package/dist/browser/index-browser.mjs.map +1 -0
  35. package/dist/browser/index.d.ts +5 -0
  36. package/dist/browser/index.js +3 -0
  37. package/dist/browser/load-parse.d.ts +20 -0
  38. package/dist/browser/load-parse.d.ts.map +1 -0
  39. package/dist/browser/load-parse.js +28 -0
  40. package/dist/browser/load-parse.js.map +1 -0
  41. package/dist/browser/load.d.ts +91 -0
  42. package/dist/browser/load.d.ts.map +1 -0
  43. package/dist/browser/load.js +129 -0
  44. package/dist/browser/load.js.map +1 -0
  45. package/dist/browser/options.d.ts +98 -0
  46. package/dist/browser/options.d.ts.map +1 -0
  47. package/dist/browser/options.js +34 -0
  48. package/dist/browser/options.js.map +1 -0
  49. package/dist/browser/package.json +3 -0
  50. package/dist/browser/parse.d.ts +18 -0
  51. package/dist/browser/parse.d.ts.map +1 -0
  52. package/dist/browser/parse.js +73 -0
  53. package/dist/browser/parse.js.map +1 -0
  54. package/dist/browser/parsers/parse5-adapter.d.ts +20 -0
  55. package/dist/browser/parsers/parse5-adapter.d.ts.map +1 -0
  56. package/dist/browser/parsers/parse5-adapter.js +50 -0
  57. package/dist/browser/parsers/parse5-adapter.js.map +1 -0
  58. package/dist/browser/slim.d.ts +25 -0
  59. package/dist/browser/slim.d.ts.map +1 -0
  60. package/dist/browser/slim.js +22 -0
  61. package/dist/browser/slim.js.map +1 -0
  62. package/dist/browser/static.d.ts +112 -0
  63. package/dist/browser/static.d.ts.map +1 -0
  64. package/dist/browser/static.js +204 -0
  65. package/dist/browser/static.js.map +1 -0
  66. package/dist/browser/types.d.ts +21 -0
  67. package/dist/browser/types.d.ts.map +1 -0
  68. package/dist/browser/types.js +3 -0
  69. package/dist/browser/types.js.map +1 -0
  70. package/dist/browser/utils.d.ts +55 -0
  71. package/dist/browser/utils.d.ts.map +1 -0
  72. package/dist/browser/utils.js +84 -0
  73. package/dist/browser/utils.js.map +1 -0
  74. package/dist/commonjs/api/attributes.d.ts +385 -0
  75. package/dist/commonjs/api/attributes.d.ts.map +1 -0
  76. package/dist/commonjs/api/attributes.js +647 -0
  77. package/dist/commonjs/api/attributes.js.map +1 -0
  78. package/dist/commonjs/api/css.d.ts +42 -0
  79. package/dist/commonjs/api/css.d.ts.map +1 -0
  80. package/dist/commonjs/api/css.js +119 -0
  81. package/dist/commonjs/api/css.js.map +1 -0
  82. package/dist/commonjs/api/extract.d.ts +27 -0
  83. package/dist/commonjs/api/extract.d.ts.map +1 -0
  84. package/dist/commonjs/api/extract.js +45 -0
  85. package/dist/commonjs/api/extract.js.map +1 -0
  86. package/dist/commonjs/api/forms.d.ts +36 -0
  87. package/dist/commonjs/api/forms.d.ts.map +1 -0
  88. package/dist/commonjs/api/forms.js +85 -0
  89. package/dist/commonjs/api/forms.js.map +1 -0
  90. package/dist/commonjs/api/manipulation.d.ts +528 -0
  91. package/dist/commonjs/api/manipulation.d.ts.map +1 -0
  92. package/dist/commonjs/api/manipulation.js +850 -0
  93. package/dist/commonjs/api/manipulation.js.map +1 -0
  94. package/dist/commonjs/api/traversing.d.ts +657 -0
  95. package/dist/commonjs/api/traversing.d.ts.map +1 -0
  96. package/dist/commonjs/api/traversing.js +914 -0
  97. package/dist/commonjs/api/traversing.js.map +1 -0
  98. package/dist/commonjs/cheerio.d.ts +85 -0
  99. package/dist/commonjs/cheerio.d.ts.map +1 -0
  100. package/dist/commonjs/cheerio.js +95 -0
  101. package/dist/commonjs/cheerio.js.map +1 -0
  102. package/dist/commonjs/index.d.ts +104 -0
  103. package/dist/commonjs/index.d.ts.map +1 -0
  104. package/dist/commonjs/index.js +250 -0
  105. package/dist/commonjs/index.js.map +1 -0
  106. package/dist/commonjs/load-parse.d.ts +20 -0
  107. package/dist/commonjs/load-parse.d.ts.map +1 -0
  108. package/dist/commonjs/load-parse.js +34 -0
  109. package/dist/commonjs/load-parse.js.map +1 -0
  110. package/dist/commonjs/load.d.ts +91 -0
  111. package/dist/commonjs/load.d.ts.map +1 -0
  112. package/dist/commonjs/load.js +165 -0
  113. package/dist/commonjs/load.js.map +1 -0
  114. package/dist/commonjs/options.d.ts +98 -0
  115. package/dist/commonjs/options.d.ts.map +1 -0
  116. package/dist/commonjs/options.js +37 -0
  117. package/dist/commonjs/options.js.map +1 -0
  118. package/dist/commonjs/package.json +3 -0
  119. package/dist/commonjs/parse.d.ts +18 -0
  120. package/dist/commonjs/parse.d.ts.map +1 -0
  121. package/dist/commonjs/parse.js +77 -0
  122. package/dist/commonjs/parse.js.map +1 -0
  123. package/dist/commonjs/parsers/parse5-adapter.d.ts +20 -0
  124. package/dist/commonjs/parsers/parse5-adapter.d.ts.map +1 -0
  125. package/dist/commonjs/parsers/parse5-adapter.js +54 -0
  126. package/dist/commonjs/parsers/parse5-adapter.js.map +1 -0
  127. package/dist/commonjs/slim.d.ts +25 -0
  128. package/dist/commonjs/slim.d.ts.map +1 -0
  129. package/dist/commonjs/slim.js +30 -0
  130. package/dist/commonjs/slim.js.map +1 -0
  131. package/dist/commonjs/static.d.ts +112 -0
  132. package/dist/commonjs/static.d.ts.map +1 -0
  133. package/dist/commonjs/static.js +214 -0
  134. package/dist/commonjs/static.js.map +1 -0
  135. package/dist/commonjs/types.d.ts +21 -0
  136. package/dist/commonjs/types.d.ts.map +1 -0
  137. package/dist/commonjs/types.js +4 -0
  138. package/dist/commonjs/types.js.map +1 -0
  139. package/dist/commonjs/utils.d.ts +55 -0
  140. package/dist/commonjs/utils.d.ts.map +1 -0
  141. package/dist/commonjs/utils.js +91 -0
  142. package/dist/commonjs/utils.js.map +1 -0
  143. package/dist/esm/api/attributes.d.ts +385 -0
  144. package/dist/esm/api/attributes.d.ts.map +1 -0
  145. package/dist/esm/api/attributes.js +636 -0
  146. package/dist/esm/api/attributes.js.map +1 -0
  147. package/dist/esm/api/css.d.ts +42 -0
  148. package/dist/esm/api/css.d.ts.map +1 -0
  149. package/dist/esm/api/css.js +116 -0
  150. package/dist/esm/api/css.js.map +1 -0
  151. package/dist/esm/api/extract.d.ts +27 -0
  152. package/dist/esm/api/extract.d.ts.map +1 -0
  153. package/dist/esm/api/extract.js +42 -0
  154. package/dist/esm/api/extract.js.map +1 -0
  155. package/dist/esm/api/forms.d.ts +36 -0
  156. package/dist/esm/api/forms.d.ts.map +1 -0
  157. package/dist/esm/api/forms.js +81 -0
  158. package/dist/esm/api/forms.js.map +1 -0
  159. package/dist/esm/api/manipulation.d.ts +528 -0
  160. package/dist/esm/api/manipulation.d.ts.map +1 -0
  161. package/dist/esm/api/manipulation.js +831 -0
  162. package/dist/esm/api/manipulation.js.map +1 -0
  163. package/dist/esm/api/traversing.d.ts +657 -0
  164. package/dist/esm/api/traversing.d.ts.map +1 -0
  165. package/dist/esm/api/traversing.js +857 -0
  166. package/dist/esm/api/traversing.js.map +1 -0
  167. package/dist/esm/cheerio.d.ts +85 -0
  168. package/dist/esm/cheerio.d.ts.map +1 -0
  169. package/dist/esm/cheerio.js +58 -0
  170. package/dist/esm/cheerio.js.map +1 -0
  171. package/dist/esm/index.d.ts +104 -0
  172. package/dist/esm/index.d.ts.map +1 -0
  173. package/dist/esm/index.js +202 -0
  174. package/dist/esm/index.js.map +1 -0
  175. package/dist/esm/load-parse.d.ts +20 -0
  176. package/dist/esm/load-parse.d.ts.map +1 -0
  177. package/dist/esm/load-parse.js +28 -0
  178. package/dist/esm/load-parse.js.map +1 -0
  179. package/dist/esm/load.d.ts +91 -0
  180. package/dist/esm/load.d.ts.map +1 -0
  181. package/dist/esm/load.js +129 -0
  182. package/dist/esm/load.js.map +1 -0
  183. package/dist/esm/options.d.ts +98 -0
  184. package/dist/esm/options.d.ts.map +1 -0
  185. package/dist/esm/options.js +34 -0
  186. package/dist/esm/options.js.map +1 -0
  187. package/dist/esm/package.json +3 -0
  188. package/dist/esm/parse.d.ts +18 -0
  189. package/dist/esm/parse.d.ts.map +1 -0
  190. package/dist/esm/parse.js +73 -0
  191. package/dist/esm/parse.js.map +1 -0
  192. package/dist/esm/parsers/parse5-adapter.d.ts +20 -0
  193. package/dist/esm/parsers/parse5-adapter.d.ts.map +1 -0
  194. package/dist/esm/parsers/parse5-adapter.js +50 -0
  195. package/dist/esm/parsers/parse5-adapter.js.map +1 -0
  196. package/dist/esm/slim.d.ts +25 -0
  197. package/dist/esm/slim.d.ts.map +1 -0
  198. package/dist/esm/slim.js +22 -0
  199. package/dist/esm/slim.js.map +1 -0
  200. package/dist/esm/static.d.ts +112 -0
  201. package/dist/esm/static.d.ts.map +1 -0
  202. package/dist/esm/static.js +204 -0
  203. package/dist/esm/static.js.map +1 -0
  204. package/dist/esm/types.d.ts +21 -0
  205. package/dist/esm/types.d.ts.map +1 -0
  206. package/dist/esm/types.js +3 -0
  207. package/dist/esm/types.js.map +1 -0
  208. package/dist/esm/utils.d.ts +55 -0
  209. package/dist/esm/utils.d.ts.map +1 -0
  210. package/dist/esm/utils.js +84 -0
  211. package/dist/esm/utils.js.map +1 -0
  212. package/package.json +219 -0
  213. package/src/api/attributes.ts +1145 -0
  214. package/src/api/css.ts +224 -0
  215. package/src/api/extract.ts +92 -0
  216. package/src/api/forms.ts +103 -0
  217. package/src/api/manipulation.ts +1115 -0
  218. package/src/api/traversing.ts +1175 -0
  219. package/src/cheerio.ts +143 -0
  220. package/src/index-browser.mts +10 -0
  221. package/src/index.ts +294 -0
  222. package/src/load-parse.ts +39 -0
  223. package/src/load.ts +282 -0
  224. package/src/options.ts +136 -0
  225. package/src/parse.ts +105 -0
  226. package/src/parsers/parse5-adapter.ts +66 -0
  227. package/src/slim.ts +33 -0
  228. package/src/static.ts +312 -0
  229. package/src/types.ts +58 -0
  230. package/src/utils.ts +99 -0
@@ -0,0 +1,1175 @@
1
+ /**
2
+ * Methods for traversing the DOM structure.
3
+ *
4
+ * @module cheerio/traversing
5
+ */
6
+
7
+ import {
8
+ isTag,
9
+ type AnyNode,
10
+ type Element,
11
+ hasChildren,
12
+ isDocument,
13
+ type Document,
14
+ } from 'domhandler';
15
+ import type { Cheerio } from '../cheerio.js';
16
+ import * as select from 'cheerio-select';
17
+ import { domEach, isCheerio } from '../utils.js';
18
+ import { contains } from '../static.js';
19
+ import {
20
+ getChildren,
21
+ getSiblings,
22
+ nextElementSibling,
23
+ prevElementSibling,
24
+ uniqueSort,
25
+ } from 'domutils';
26
+ import type { FilterFunction, AcceptedFilters } from '../types.js';
27
+ const reContextSelector = /^\s*(?:[+~]|:scope\b)/;
28
+
29
+ /**
30
+ * Get the descendants of each element in the current set of matched elements,
31
+ * filtered by a selector, jQuery object, or element.
32
+ *
33
+ * @category Traversing
34
+ * @example
35
+ *
36
+ * ```js
37
+ * $('#fruits').find('li').length;
38
+ * //=> 3
39
+ * $('#fruits').find($('.apple')).length;
40
+ * //=> 1
41
+ * ```
42
+ *
43
+ * @param selectorOrHaystack - Element to look for.
44
+ * @returns The found elements.
45
+ * @see {@link https://api.jquery.com/find/}
46
+ */
47
+ export function find<T extends AnyNode>(
48
+ this: Cheerio<T>,
49
+ selectorOrHaystack?: string | Cheerio<Element> | Element,
50
+ ): Cheerio<Element> {
51
+ if (!selectorOrHaystack) {
52
+ return this._make([]);
53
+ }
54
+
55
+ if (typeof selectorOrHaystack !== 'string') {
56
+ const haystack = isCheerio(selectorOrHaystack)
57
+ ? selectorOrHaystack.toArray()
58
+ : [selectorOrHaystack];
59
+
60
+ const context = this.toArray();
61
+
62
+ return this._make(
63
+ haystack.filter((elem) => context.some((node) => contains(node, elem))),
64
+ );
65
+ }
66
+
67
+ return this._findBySelector(selectorOrHaystack, Number.POSITIVE_INFINITY);
68
+ }
69
+
70
+ /**
71
+ * Find elements by a specific selector.
72
+ *
73
+ * @private
74
+ * @category Traversing
75
+ * @param selector - Selector to filter by.
76
+ * @param limit - Maximum number of elements to match.
77
+ * @returns The found elements.
78
+ */
79
+ export function _findBySelector<T extends AnyNode>(
80
+ this: Cheerio<T>,
81
+ selector: string,
82
+ limit: number,
83
+ ): Cheerio<Element> {
84
+ const context = this.toArray();
85
+
86
+ const elems = reContextSelector.test(selector)
87
+ ? context
88
+ : this.children().toArray();
89
+
90
+ const options = {
91
+ context,
92
+ root: this._root?.[0],
93
+
94
+ // Pass options that are recognized by `cheerio-select`
95
+ xmlMode: this.options.xmlMode,
96
+ lowerCaseTags: this.options.lowerCaseTags,
97
+ lowerCaseAttributeNames: this.options.lowerCaseAttributeNames,
98
+ pseudos: this.options.pseudos,
99
+ quirksMode: this.options.quirksMode,
100
+ };
101
+
102
+ return this._make(select.select(selector, elems, options, limit));
103
+ }
104
+
105
+ /**
106
+ * Creates a matcher, using a particular mapping function. Matchers provide a
107
+ * function that finds elements using a generating function, supporting
108
+ * filtering.
109
+ *
110
+ * @private
111
+ * @param matchMap - Mapping function.
112
+ * @returns - Function for wrapping generating functions.
113
+ */
114
+ function _getMatcher<P>(
115
+ matchMap: (fn: (elem: AnyNode) => P, elems: Cheerio<AnyNode>) => Element[],
116
+ ) {
117
+ return function (
118
+ fn: (elem: AnyNode) => P,
119
+ ...postFns: ((elems: Element[]) => Element[])[]
120
+ ) {
121
+ return function <T extends AnyNode>(
122
+ this: Cheerio<T>,
123
+ selector?: AcceptedFilters<Element>,
124
+ ): Cheerio<Element> {
125
+ let matched: Element[] = matchMap(fn, this);
126
+
127
+ if (selector) {
128
+ matched = filterArray(
129
+ matched,
130
+ selector,
131
+ this.options.xmlMode,
132
+ this._root?.[0],
133
+ );
134
+ }
135
+
136
+ return this._make(
137
+ // Post processing is only necessary if there is more than one element.
138
+ this.length > 1 && matched.length > 1
139
+ ? postFns.reduce((elems, fn) => fn(elems), matched)
140
+ : matched,
141
+ );
142
+ };
143
+ };
144
+ }
145
+
146
+ /** Matcher that adds multiple elements for each entry in the input. */
147
+ const _matcher = _getMatcher((fn: (elem: AnyNode) => Element[], elems) => {
148
+ let ret: Element[] = [];
149
+
150
+ for (let i = 0; i < elems.length; i++) {
151
+ const value = fn(elems[i]);
152
+ if (value.length > 0) ret = ret.concat(value);
153
+ }
154
+
155
+ return ret;
156
+ });
157
+
158
+ /** Matcher that adds at most one element for each entry in the input. */
159
+ const _singleMatcher = _getMatcher(
160
+ (fn: (elem: AnyNode) => Element | null, elems) => {
161
+ const ret: Element[] = [];
162
+
163
+ for (let i = 0; i < elems.length; i++) {
164
+ const value = fn(elems[i]);
165
+ if (value !== null) {
166
+ ret.push(value);
167
+ }
168
+ }
169
+ return ret;
170
+ },
171
+ );
172
+
173
+ /**
174
+ * Matcher that supports traversing until a condition is met.
175
+ *
176
+ * @param nextElem - Function that returns the next element.
177
+ * @param postFns - Post processing functions.
178
+ * @returns A function usable for `*Until` methods.
179
+ */
180
+ function _matchUntil(
181
+ nextElem: (elem: AnyNode) => Element | null,
182
+ ...postFns: ((elems: Element[]) => Element[])[]
183
+ ) {
184
+ // We use a variable here that is used from within the matcher.
185
+ let matches: ((el: Element, i: number) => boolean) | null = null;
186
+
187
+ const innerMatcher = _getMatcher(
188
+ (nextElem: (elem: AnyNode) => Element | null, elems) => {
189
+ const matched: Element[] = [];
190
+
191
+ domEach(elems, (elem) => {
192
+ for (let next; (next = nextElem(elem)); elem = next) {
193
+ // FIXME: `matched` might contain duplicates here and the index is too large.
194
+ if (matches?.(next, matched.length)) break;
195
+ matched.push(next);
196
+ }
197
+ });
198
+
199
+ return matched;
200
+ },
201
+ )(nextElem, ...postFns);
202
+
203
+ return function <T extends AnyNode>(
204
+ this: Cheerio<T>,
205
+ selector?: AcceptedFilters<Element> | null,
206
+ filterSelector?: AcceptedFilters<Element>,
207
+ ): Cheerio<Element> {
208
+ // Override `matches` variable with the new target.
209
+ matches =
210
+ typeof selector === 'string'
211
+ ? (elem: Element) => select.is(elem, selector, this.options)
212
+ : selector
213
+ ? getFilterFn(selector)
214
+ : null;
215
+
216
+ const ret = innerMatcher.call(this, filterSelector);
217
+
218
+ // Set `matches` to `null`, so we don't waste memory.
219
+ matches = null;
220
+
221
+ return ret;
222
+ };
223
+ }
224
+
225
+ function _removeDuplicates<T extends AnyNode>(elems: T[]): T[] {
226
+ return elems.length > 1 ? Array.from(new Set<T>(elems)) : elems;
227
+ }
228
+
229
+ /**
230
+ * Get the parent of each element in the current set of matched elements,
231
+ * optionally filtered by a selector.
232
+ *
233
+ * @category Traversing
234
+ * @example
235
+ *
236
+ * ```js
237
+ * $('.pear').parent().attr('id');
238
+ * //=> fruits
239
+ * ```
240
+ *
241
+ * @param selector - If specified filter for parent.
242
+ * @returns The parents.
243
+ * @see {@link https://api.jquery.com/parent/}
244
+ */
245
+ export const parent: <T extends AnyNode>(
246
+ this: Cheerio<T>,
247
+ selector?: AcceptedFilters<Element>,
248
+ ) => Cheerio<Element> = _singleMatcher(
249
+ ({ parent }) => (parent && !isDocument(parent) ? (parent as Element) : null),
250
+ _removeDuplicates,
251
+ );
252
+
253
+ /**
254
+ * Get a set of parents filtered by `selector` of each element in the current
255
+ * set of match elements.
256
+ *
257
+ * @category Traversing
258
+ * @example
259
+ *
260
+ * ```js
261
+ * $('.orange').parents().length;
262
+ * //=> 2
263
+ * $('.orange').parents('#fruits').length;
264
+ * //=> 1
265
+ * ```
266
+ *
267
+ * @param selector - If specified filter for parents.
268
+ * @returns The parents.
269
+ * @see {@link https://api.jquery.com/parents/}
270
+ */
271
+ export const parents: <T extends AnyNode>(
272
+ this: Cheerio<T>,
273
+ selector?: AcceptedFilters<Element>,
274
+ ) => Cheerio<Element> = _matcher(
275
+ (elem) => {
276
+ const matched: Element[] = [];
277
+ while (elem.parent && !isDocument(elem.parent)) {
278
+ matched.push(elem.parent as Element);
279
+ elem = elem.parent;
280
+ }
281
+ return matched;
282
+ },
283
+ uniqueSort,
284
+ // eslint-disable-next-line unicorn/no-array-reverse
285
+ (elems) => elems.reverse(),
286
+ );
287
+
288
+ /**
289
+ * Get the ancestors of each element in the current set of matched elements, up
290
+ * to but not including the element matched by the selector, DOM node, or
291
+ * cheerio object.
292
+ *
293
+ * @category Traversing
294
+ * @example
295
+ *
296
+ * ```js
297
+ * $('.orange').parentsUntil('#food').length;
298
+ * //=> 1
299
+ * ```
300
+ *
301
+ * @param selector - Selector for element to stop at.
302
+ * @param filterSelector - Optional filter for parents.
303
+ * @returns The parents.
304
+ * @see {@link https://api.jquery.com/parentsUntil/}
305
+ */
306
+ export const parentsUntil: <T extends AnyNode>(
307
+ this: Cheerio<T>,
308
+ selector?: AcceptedFilters<Element> | null,
309
+ filterSelector?: AcceptedFilters<Element>,
310
+ ) => Cheerio<Element> = _matchUntil(
311
+ ({ parent }) => (parent && !isDocument(parent) ? (parent as Element) : null),
312
+ uniqueSort,
313
+ // eslint-disable-next-line unicorn/no-array-reverse
314
+ (elems) => elems.reverse(),
315
+ );
316
+
317
+ /**
318
+ * For each element in the set, get the first element that matches the selector
319
+ * by testing the element itself and traversing up through its ancestors in the
320
+ * DOM tree.
321
+ *
322
+ * @category Traversing
323
+ * @example
324
+ *
325
+ * ```js
326
+ * $('.orange').closest();
327
+ * //=> []
328
+ *
329
+ * $('.orange').closest('.apple');
330
+ * // => []
331
+ *
332
+ * $('.orange').closest('li');
333
+ * //=> [<li class="orange">Orange</li>]
334
+ *
335
+ * $('.orange').closest('#fruits');
336
+ * //=> [<ul id="fruits"> ... </ul>]
337
+ * ```
338
+ *
339
+ * @param selector - Selector for the element to find.
340
+ * @returns The closest nodes.
341
+ * @see {@link https://api.jquery.com/closest/}
342
+ */
343
+ export function closest<T extends AnyNode>(
344
+ this: Cheerio<T>,
345
+ selector?: AcceptedFilters<Element>,
346
+ ): Cheerio<AnyNode> {
347
+ const set: AnyNode[] = [];
348
+
349
+ if (!selector) {
350
+ return this._make(set);
351
+ }
352
+
353
+ const selectOpts = {
354
+ xmlMode: this.options.xmlMode,
355
+ root: this._root?.[0],
356
+ };
357
+
358
+ const selectFn =
359
+ typeof selector === 'string'
360
+ ? (elem: Element) => select.is(elem, selector, selectOpts)
361
+ : getFilterFn(selector);
362
+
363
+ domEach(this, (elem: AnyNode | null) => {
364
+ if (elem && !isDocument(elem) && !isTag(elem)) {
365
+ elem = elem.parent;
366
+ }
367
+ while (elem && isTag(elem)) {
368
+ if (selectFn(elem, 0)) {
369
+ // Do not add duplicate elements to the set
370
+ if (!set.includes(elem)) {
371
+ set.push(elem);
372
+ }
373
+ break;
374
+ }
375
+ elem = elem.parent;
376
+ }
377
+ });
378
+
379
+ return this._make(set);
380
+ }
381
+
382
+ /**
383
+ * Gets the next sibling of each selected element, optionally filtered by a
384
+ * selector.
385
+ *
386
+ * @category Traversing
387
+ * @example
388
+ *
389
+ * ```js
390
+ * $('.apple').next().hasClass('orange');
391
+ * //=> true
392
+ * ```
393
+ *
394
+ * @param selector - If specified filter for sibling.
395
+ * @returns The next nodes.
396
+ * @see {@link https://api.jquery.com/next/}
397
+ */
398
+ export const next: <T extends AnyNode>(
399
+ this: Cheerio<T>,
400
+ selector?: AcceptedFilters<Element>,
401
+ ) => Cheerio<Element> = _singleMatcher((elem) => nextElementSibling(elem));
402
+
403
+ /**
404
+ * Gets all the following siblings of the each selected element, optionally
405
+ * filtered by a selector.
406
+ *
407
+ * @category Traversing
408
+ * @example
409
+ *
410
+ * ```js
411
+ * $('.apple').nextAll();
412
+ * //=> [<li class="orange">Orange</li>, <li class="pear">Pear</li>]
413
+ * $('.apple').nextAll('.orange');
414
+ * //=> [<li class="orange">Orange</li>]
415
+ * ```
416
+ *
417
+ * @param selector - If specified filter for siblings.
418
+ * @returns The next nodes.
419
+ * @see {@link https://api.jquery.com/nextAll/}
420
+ */
421
+ export const nextAll: <T extends AnyNode>(
422
+ this: Cheerio<T>,
423
+ selector?: AcceptedFilters<Element>,
424
+ ) => Cheerio<Element> = _matcher((elem) => {
425
+ const matched = [];
426
+ while (elem.next) {
427
+ elem = elem.next;
428
+ if (isTag(elem)) matched.push(elem);
429
+ }
430
+ return matched;
431
+ }, _removeDuplicates);
432
+
433
+ /**
434
+ * Gets all the following siblings up to but not including the element matched
435
+ * by the selector, optionally filtered by another selector.
436
+ *
437
+ * @category Traversing
438
+ * @example
439
+ *
440
+ * ```js
441
+ * $('.apple').nextUntil('.pear');
442
+ * //=> [<li class="orange">Orange</li>]
443
+ * ```
444
+ *
445
+ * @param selector - Selector for element to stop at.
446
+ * @param filterSelector - If specified filter for siblings.
447
+ * @returns The next nodes.
448
+ * @see {@link https://api.jquery.com/nextUntil/}
449
+ */
450
+ export const nextUntil: <T extends AnyNode>(
451
+ this: Cheerio<T>,
452
+ selector?: AcceptedFilters<Element> | null,
453
+ filterSelector?: AcceptedFilters<Element>,
454
+ ) => Cheerio<Element> = _matchUntil(
455
+ (el) => nextElementSibling(el),
456
+ _removeDuplicates,
457
+ );
458
+
459
+ /**
460
+ * Gets the previous sibling of each selected element optionally filtered by a
461
+ * selector.
462
+ *
463
+ * @category Traversing
464
+ * @example
465
+ *
466
+ * ```js
467
+ * $('.orange').prev().hasClass('apple');
468
+ * //=> true
469
+ * ```
470
+ *
471
+ * @param selector - If specified filter for siblings.
472
+ * @returns The previous nodes.
473
+ * @see {@link https://api.jquery.com/prev/}
474
+ */
475
+ export const prev: <T extends AnyNode>(
476
+ this: Cheerio<T>,
477
+ selector?: AcceptedFilters<Element>,
478
+ ) => Cheerio<Element> = _singleMatcher((elem) => prevElementSibling(elem));
479
+
480
+ /**
481
+ * Gets all the preceding siblings of each selected element, optionally filtered
482
+ * by a selector.
483
+ *
484
+ * @category Traversing
485
+ * @example
486
+ *
487
+ * ```js
488
+ * $('.pear').prevAll();
489
+ * //=> [<li class="orange">Orange</li>, <li class="apple">Apple</li>]
490
+ *
491
+ * $('.pear').prevAll('.orange');
492
+ * //=> [<li class="orange">Orange</li>]
493
+ * ```
494
+ *
495
+ * @param selector - If specified filter for siblings.
496
+ * @returns The previous nodes.
497
+ * @see {@link https://api.jquery.com/prevAll/}
498
+ */
499
+ export const prevAll: <T extends AnyNode>(
500
+ this: Cheerio<T>,
501
+ selector?: AcceptedFilters<Element>,
502
+ ) => Cheerio<Element> = _matcher((elem) => {
503
+ const matched = [];
504
+ while (elem.prev) {
505
+ elem = elem.prev;
506
+ if (isTag(elem)) matched.push(elem);
507
+ }
508
+ return matched;
509
+ }, _removeDuplicates);
510
+
511
+ /**
512
+ * Gets all the preceding siblings up to but not including the element matched
513
+ * by the selector, optionally filtered by another selector.
514
+ *
515
+ * @category Traversing
516
+ * @example
517
+ *
518
+ * ```js
519
+ * $('.pear').prevUntil('.apple');
520
+ * //=> [<li class="orange">Orange</li>]
521
+ * ```
522
+ *
523
+ * @param selector - Selector for element to stop at.
524
+ * @param filterSelector - If specified filter for siblings.
525
+ * @returns The previous nodes.
526
+ * @see {@link https://api.jquery.com/prevUntil/}
527
+ */
528
+ export const prevUntil: <T extends AnyNode>(
529
+ this: Cheerio<T>,
530
+ selector?: AcceptedFilters<Element> | null,
531
+ filterSelector?: AcceptedFilters<Element>,
532
+ ) => Cheerio<Element> = _matchUntil(
533
+ (el) => prevElementSibling(el),
534
+ _removeDuplicates,
535
+ );
536
+
537
+ /**
538
+ * Get the siblings of each element (excluding the element) in the set of
539
+ * matched elements, optionally filtered by a selector.
540
+ *
541
+ * @category Traversing
542
+ * @example
543
+ *
544
+ * ```js
545
+ * $('.pear').siblings().length;
546
+ * //=> 2
547
+ *
548
+ * $('.pear').siblings('.orange').length;
549
+ * //=> 1
550
+ * ```
551
+ *
552
+ * @param selector - If specified filter for siblings.
553
+ * @returns The siblings.
554
+ * @see {@link https://api.jquery.com/siblings/}
555
+ */
556
+ export const siblings: <T extends AnyNode>(
557
+ this: Cheerio<T>,
558
+ selector?: AcceptedFilters<Element>,
559
+ ) => Cheerio<Element> = _matcher(
560
+ (elem) =>
561
+ getSiblings(elem).filter((el): el is Element => isTag(el) && el !== elem),
562
+ uniqueSort,
563
+ );
564
+
565
+ /**
566
+ * Gets the element children of each element in the set of matched elements.
567
+ *
568
+ * @category Traversing
569
+ * @example
570
+ *
571
+ * ```js
572
+ * $('#fruits').children().length;
573
+ * //=> 3
574
+ *
575
+ * $('#fruits').children('.pear').text();
576
+ * //=> Pear
577
+ * ```
578
+ *
579
+ * @param selector - If specified filter for children.
580
+ * @returns The children.
581
+ * @see {@link https://api.jquery.com/children/}
582
+ */
583
+ export const children: <T extends AnyNode>(
584
+ this: Cheerio<T>,
585
+ selector?: AcceptedFilters<Element>,
586
+ ) => Cheerio<Element> = _matcher(
587
+ (elem) => getChildren(elem).filter(isTag),
588
+ _removeDuplicates,
589
+ );
590
+
591
+ /**
592
+ * Gets the children of each element in the set of matched elements, including
593
+ * text and comment nodes.
594
+ *
595
+ * @category Traversing
596
+ * @example
597
+ *
598
+ * ```js
599
+ * $('#fruits').contents().length;
600
+ * //=> 3
601
+ * ```
602
+ *
603
+ * @returns The children.
604
+ * @see {@link https://api.jquery.com/contents/}
605
+ */
606
+ export function contents<T extends AnyNode>(
607
+ this: Cheerio<T>,
608
+ ): Cheerio<AnyNode> {
609
+ const elems = this.toArray().reduce<AnyNode[]>(
610
+ (newElems, elem) =>
611
+ hasChildren(elem) ? newElems.concat(elem.children) : newElems,
612
+ [],
613
+ );
614
+ return this._make(elems);
615
+ }
616
+
617
+ /**
618
+ * Iterates over a cheerio object, executing a function for each matched
619
+ * element. When the callback is fired, the function is fired in the context of
620
+ * the DOM element, so `this` refers to the current element, which is equivalent
621
+ * to the function parameter `element`. To break out of the `each` loop early,
622
+ * return with `false`.
623
+ *
624
+ * @category Traversing
625
+ * @example
626
+ *
627
+ * ```js
628
+ * const fruits = [];
629
+ *
630
+ * $('li').each(function (i, elem) {
631
+ * fruits[i] = $(this).text();
632
+ * });
633
+ *
634
+ * fruits.join(', ');
635
+ * //=> Apple, Orange, Pear
636
+ * ```
637
+ *
638
+ * @param fn - Function to execute.
639
+ * @returns The instance itself, useful for chaining.
640
+ * @see {@link https://api.jquery.com/each/}
641
+ */
642
+ export function each<T>(
643
+ this: Cheerio<T>,
644
+ fn: (this: T, i: number, el: T) => void | boolean,
645
+ ): Cheerio<T> {
646
+ let i = 0;
647
+ const len = this.length;
648
+ while (i < len && fn.call(this[i], i, this[i]) !== false) ++i;
649
+ return this;
650
+ }
651
+
652
+ /**
653
+ * Pass each element in the current matched set through a function, producing a
654
+ * new Cheerio object containing the return values. The function can return an
655
+ * individual data item or an array of data items to be inserted into the
656
+ * resulting set. If an array is returned, the elements inside the array are
657
+ * inserted into the set. If the function returns null or undefined, no element
658
+ * will be inserted.
659
+ *
660
+ * @category Traversing
661
+ * @example
662
+ *
663
+ * ```js
664
+ * $('li')
665
+ * .map(function (i, el) {
666
+ * // this === el
667
+ * return $(this).text();
668
+ * })
669
+ * .toArray()
670
+ * .join(' ');
671
+ * //=> "apple orange pear"
672
+ * ```
673
+ *
674
+ * @param fn - Function to execute.
675
+ * @returns The mapped elements, wrapped in a Cheerio collection.
676
+ * @see {@link https://api.jquery.com/map/}
677
+ */
678
+ export function map<T, M>(
679
+ this: Cheerio<T>,
680
+ fn: (this: T, i: number, el: T) => M[] | M | null | undefined,
681
+ ): Cheerio<M> {
682
+ let elems: M[] = [];
683
+ for (let i = 0; i < this.length; i++) {
684
+ const el = this[i];
685
+ const val = fn.call(el, i, el);
686
+ if (val != null) {
687
+ elems = elems.concat(val);
688
+ }
689
+ }
690
+ return this._make(elems);
691
+ }
692
+
693
+ /**
694
+ * Creates a function to test if a filter is matched.
695
+ *
696
+ * @param match - A filter.
697
+ * @returns A function that determines if a filter has been matched.
698
+ */
699
+ function getFilterFn<T>(
700
+ match: FilterFunction<T> | Cheerio<T> | T,
701
+ ): (el: T, i: number) => boolean {
702
+ if (typeof match === 'function') {
703
+ return (el, i) => (match as FilterFunction<T>).call(el, i, el);
704
+ }
705
+ if (isCheerio<T>(match)) {
706
+ return (el) => Array.prototype.includes.call(match, el);
707
+ }
708
+ return function (el) {
709
+ return match === el;
710
+ };
711
+ }
712
+
713
+ /**
714
+ * Iterates over a cheerio object, reducing the set of selector elements to
715
+ * those that match the selector or pass the function's test.
716
+ *
717
+ * This is the definition for using type guards; have a look below for other
718
+ * ways to invoke this method. The function is executed in the context of the
719
+ * selected element, so `this` refers to the current element.
720
+ *
721
+ * @category Traversing
722
+ * @example <caption>Function</caption>
723
+ *
724
+ * ```js
725
+ * $('li')
726
+ * .filter(function (i, el) {
727
+ * // this === el
728
+ * return $(this).attr('class') === 'orange';
729
+ * })
730
+ * .attr('class'); //=> orange
731
+ * ```
732
+ *
733
+ * @param match - Value to look for, following the rules above.
734
+ * @returns The filtered collection.
735
+ * @see {@link https://api.jquery.com/filter/}
736
+ */
737
+ export function filter<T, S extends T>(
738
+ this: Cheerio<T>,
739
+ match: (this: T, index: number, value: T) => value is S,
740
+ ): Cheerio<S>;
741
+ /**
742
+ * Iterates over a cheerio object, reducing the set of selector elements to
743
+ * those that match the selector or pass the function's test.
744
+ *
745
+ * - When a Cheerio selection is specified, return only the elements contained in
746
+ * that selection.
747
+ * - When an element is specified, return only that element (if it is contained in
748
+ * the original selection).
749
+ * - If using the function method, the function is executed in the context of the
750
+ * selected element, so `this` refers to the current element.
751
+ *
752
+ * @category Traversing
753
+ * @example <caption>Selector</caption>
754
+ *
755
+ * ```js
756
+ * $('li').filter('.orange').attr('class');
757
+ * //=> orange
758
+ * ```
759
+ *
760
+ * @example <caption>Function</caption>
761
+ *
762
+ * ```js
763
+ * $('li')
764
+ * .filter(function (i, el) {
765
+ * // this === el
766
+ * return $(this).attr('class') === 'orange';
767
+ * })
768
+ * .attr('class'); //=> orange
769
+ * ```
770
+ *
771
+ * @param match - Value to look for, following the rules above. See
772
+ * {@link AcceptedFilters}.
773
+ * @returns The filtered collection.
774
+ * @see {@link https://api.jquery.com/filter/}
775
+ */
776
+ export function filter<T, S extends AcceptedFilters<T>>(
777
+ this: Cheerio<T>,
778
+ match: S,
779
+ ): Cheerio<S extends string ? Element : T>;
780
+ export function filter<T>(
781
+ this: Cheerio<T>,
782
+ match: AcceptedFilters<T>,
783
+ ): Cheerio<unknown> {
784
+ return this._make<unknown>(
785
+ filterArray(this.toArray(), match, this.options.xmlMode, this._root?.[0]),
786
+ );
787
+ }
788
+
789
+ export function filterArray<T>(
790
+ nodes: T[],
791
+ match: AcceptedFilters<T>,
792
+ xmlMode?: boolean,
793
+ root?: Document,
794
+ ): Element[] | T[] {
795
+ return typeof match === 'string'
796
+ ? select.filter(match, nodes as unknown as AnyNode[], { xmlMode, root })
797
+ : nodes.filter(getFilterFn<T>(match));
798
+ }
799
+
800
+ /**
801
+ * Checks the current list of elements and returns `true` if _any_ of the
802
+ * elements match the selector. If using an element or Cheerio selection,
803
+ * returns `true` if _any_ of the elements match. If using a predicate function,
804
+ * the function is executed in the context of the selected element, so `this`
805
+ * refers to the current element.
806
+ *
807
+ * @category Traversing
808
+ * @param selector - Selector for the selection.
809
+ * @returns Whether or not the selector matches an element of the instance.
810
+ * @see {@link https://api.jquery.com/is/}
811
+ */
812
+ export function is<T>(
813
+ this: Cheerio<T>,
814
+ selector?: AcceptedFilters<T>,
815
+ ): boolean {
816
+ const nodes = this.toArray();
817
+ return typeof selector === 'string'
818
+ ? select.some(
819
+ (nodes as unknown as AnyNode[]).filter(isTag),
820
+ selector,
821
+ this.options,
822
+ )
823
+ : selector
824
+ ? nodes.some(getFilterFn<T>(selector))
825
+ : false;
826
+ }
827
+
828
+ /**
829
+ * Remove elements from the set of matched elements. Given a Cheerio object that
830
+ * represents a set of DOM elements, the `.not()` method constructs a new
831
+ * Cheerio object from a subset of the matching elements. The supplied selector
832
+ * is tested against each element; the elements that don't match the selector
833
+ * will be included in the result.
834
+ *
835
+ * The `.not()` method can take a function as its argument in the same way that
836
+ * `.filter()` does. Elements for which the function returns `true` are excluded
837
+ * from the filtered set; all other elements are included.
838
+ *
839
+ * @category Traversing
840
+ * @example <caption>Selector</caption>
841
+ *
842
+ * ```js
843
+ * $('li').not('.apple').length;
844
+ * //=> 2
845
+ * ```
846
+ *
847
+ * @example <caption>Function</caption>
848
+ *
849
+ * ```js
850
+ * $('li').not(function (i, el) {
851
+ * // this === el
852
+ * return $(this).attr('class') === 'orange';
853
+ * }).length; //=> 2
854
+ * ```
855
+ *
856
+ * @param match - Value to look for, following the rules above.
857
+ * @returns The filtered collection.
858
+ * @see {@link https://api.jquery.com/not/}
859
+ */
860
+ export function not<T extends AnyNode>(
861
+ this: Cheerio<T>,
862
+ match: AcceptedFilters<T>,
863
+ ): Cheerio<T> {
864
+ let nodes = this.toArray();
865
+
866
+ if (typeof match === 'string') {
867
+ const matches = new Set<AnyNode>(select.filter(match, nodes, this.options));
868
+ nodes = nodes.filter((el) => !matches.has(el));
869
+ } else {
870
+ const filterFn = getFilterFn(match);
871
+ nodes = nodes.filter((el, i) => !filterFn(el, i));
872
+ }
873
+
874
+ return this._make(nodes);
875
+ }
876
+
877
+ /**
878
+ * Filters the set of matched elements to only those which have the given DOM
879
+ * element as a descendant or which have a descendant that matches the given
880
+ * selector. Equivalent to `.filter(':has(selector)')`.
881
+ *
882
+ * @category Traversing
883
+ * @example <caption>Selector</caption>
884
+ *
885
+ * ```js
886
+ * $('ul').has('.pear').attr('id');
887
+ * //=> fruits
888
+ * ```
889
+ *
890
+ * @example <caption>Element</caption>
891
+ *
892
+ * ```js
893
+ * $('ul').has($('.pear')[0]).attr('id');
894
+ * //=> fruits
895
+ * ```
896
+ *
897
+ * @param selectorOrHaystack - Element to look for.
898
+ * @returns The filtered collection.
899
+ * @see {@link https://api.jquery.com/has/}
900
+ */
901
+ export function has(
902
+ this: Cheerio<AnyNode | Element>,
903
+ selectorOrHaystack: string | Cheerio<Element> | Element,
904
+ ): Cheerio<AnyNode | Element> {
905
+ return this.filter(
906
+ typeof selectorOrHaystack === 'string'
907
+ ? // Using the `:has` selector here short-circuits searches.
908
+ `:has(${selectorOrHaystack})`
909
+ : (_, el) => this._make(el).find(selectorOrHaystack).length > 0,
910
+ );
911
+ }
912
+
913
+ /**
914
+ * Will select the first element of a cheerio object.
915
+ *
916
+ * @category Traversing
917
+ * @example
918
+ *
919
+ * ```js
920
+ * $('#fruits').children().first().text();
921
+ * //=> Apple
922
+ * ```
923
+ *
924
+ * @returns The first element.
925
+ * @see {@link https://api.jquery.com/first/}
926
+ */
927
+ export function first<T extends AnyNode>(this: Cheerio<T>): Cheerio<T> {
928
+ return this.length > 1 ? this._make(this[0]) : this;
929
+ }
930
+
931
+ /**
932
+ * Will select the last element of a cheerio object.
933
+ *
934
+ * @category Traversing
935
+ * @example
936
+ *
937
+ * ```js
938
+ * $('#fruits').children().last().text();
939
+ * //=> Pear
940
+ * ```
941
+ *
942
+ * @returns The last element.
943
+ * @see {@link https://api.jquery.com/last/}
944
+ */
945
+ export function last<T>(this: Cheerio<T>): Cheerio<T> {
946
+ return this.length > 0 ? this._make(this[this.length - 1]) : this;
947
+ }
948
+
949
+ /**
950
+ * Reduce the set of matched elements to the one at the specified index. Use
951
+ * `.eq(-i)` to count backwards from the last selected element.
952
+ *
953
+ * @category Traversing
954
+ * @example
955
+ *
956
+ * ```js
957
+ * $('li').eq(0).text();
958
+ * //=> Apple
959
+ *
960
+ * $('li').eq(-1).text();
961
+ * //=> Pear
962
+ * ```
963
+ *
964
+ * @param i - Index of the element to select.
965
+ * @returns The element at the `i`th position.
966
+ * @see {@link https://api.jquery.com/eq/}
967
+ */
968
+ export function eq<T>(this: Cheerio<T>, i: number): Cheerio<T> {
969
+ i = +i;
970
+
971
+ // Use the first identity optimization if possible
972
+ if (i === 0 && this.length <= 1) return this;
973
+
974
+ if (i < 0) i = this.length + i;
975
+ return this._make(this[i] ?? []);
976
+ }
977
+
978
+ /**
979
+ * Retrieve one of the elements matched by the Cheerio object, at the `i`th
980
+ * position.
981
+ *
982
+ * @category Traversing
983
+ * @example
984
+ *
985
+ * ```js
986
+ * $('li').get(0).tagName;
987
+ * //=> li
988
+ * ```
989
+ *
990
+ * @param i - Element to retrieve.
991
+ * @returns The element at the `i`th position.
992
+ * @see {@link https://api.jquery.com/get/}
993
+ */
994
+ export function get<T>(this: Cheerio<T>, i: number): T | undefined;
995
+ /**
996
+ * Retrieve all elements matched by the Cheerio object, as an array.
997
+ *
998
+ * @category Traversing
999
+ * @example
1000
+ *
1001
+ * ```js
1002
+ * $('li').get().length;
1003
+ * //=> 3
1004
+ * ```
1005
+ *
1006
+ * @returns All elements matched by the Cheerio object.
1007
+ * @see {@link https://api.jquery.com/get/}
1008
+ */
1009
+ export function get<T>(this: Cheerio<T>): T[];
1010
+ export function get<T>(this: Cheerio<T>, i?: number): T | T[] {
1011
+ if (i == null) {
1012
+ return this.toArray();
1013
+ }
1014
+ return this[i < 0 ? this.length + i : i];
1015
+ }
1016
+
1017
+ /**
1018
+ * Retrieve all the DOM elements contained in the jQuery set as an array.
1019
+ *
1020
+ * @example
1021
+ *
1022
+ * ```js
1023
+ * $('li').toArray();
1024
+ * //=> [ {...}, {...}, {...} ]
1025
+ * ```
1026
+ *
1027
+ * @returns The contained items.
1028
+ */
1029
+ export function toArray<T>(this: Cheerio<T>): T[] {
1030
+ return (Array.prototype as T[]).slice.call(this);
1031
+ }
1032
+
1033
+ /**
1034
+ * Search for a given element from among the matched elements.
1035
+ *
1036
+ * @category Traversing
1037
+ * @example
1038
+ *
1039
+ * ```js
1040
+ * $('.pear').index();
1041
+ * //=> 2 $('.orange').index('li');
1042
+ * //=> 1
1043
+ * $('.apple').index($('#fruit, li'));
1044
+ * //=> 1
1045
+ * ```
1046
+ *
1047
+ * @param selectorOrNeedle - Element to look for.
1048
+ * @returns The index of the element.
1049
+ * @see {@link https://api.jquery.com/index/}
1050
+ */
1051
+ export function index<T extends AnyNode>(
1052
+ this: Cheerio<T>,
1053
+ selectorOrNeedle?: string | Cheerio<AnyNode> | AnyNode,
1054
+ ): number {
1055
+ let $haystack: Cheerio<AnyNode>;
1056
+ let needle: AnyNode;
1057
+
1058
+ if (selectorOrNeedle == null) {
1059
+ $haystack = this.parent().children();
1060
+ needle = this[0];
1061
+ } else if (typeof selectorOrNeedle === 'string') {
1062
+ $haystack = this._make<AnyNode>(selectorOrNeedle);
1063
+ needle = this[0];
1064
+ } else {
1065
+ // eslint-disable-next-line @typescript-eslint/no-this-alias, unicorn/no-this-assignment
1066
+ $haystack = this;
1067
+ needle = isCheerio(selectorOrNeedle)
1068
+ ? selectorOrNeedle[0]
1069
+ : selectorOrNeedle;
1070
+ }
1071
+
1072
+ return Array.prototype.indexOf.call($haystack, needle);
1073
+ }
1074
+
1075
+ /**
1076
+ * Gets the elements matching the specified range (0-based position).
1077
+ *
1078
+ * @category Traversing
1079
+ * @example
1080
+ *
1081
+ * ```js
1082
+ * $('li').slice(1).eq(0).text();
1083
+ * //=> 'Orange'
1084
+ *
1085
+ * $('li').slice(1, 2).length;
1086
+ * //=> 1
1087
+ * ```
1088
+ *
1089
+ * @param start - A position at which the elements begin to be selected. If
1090
+ * negative, it indicates an offset from the end of the set.
1091
+ * @param end - A position at which the elements stop being selected. If
1092
+ * negative, it indicates an offset from the end of the set. If omitted, the
1093
+ * range continues until the end of the set.
1094
+ * @returns The elements matching the specified range.
1095
+ * @see {@link https://api.jquery.com/slice/}
1096
+ */
1097
+ export function slice<T>(
1098
+ this: Cheerio<T>,
1099
+ start?: number,
1100
+ end?: number,
1101
+ ): Cheerio<T> {
1102
+ return this._make<T>(Array.prototype.slice.call(this, start, end));
1103
+ }
1104
+
1105
+ /**
1106
+ * End the most recent filtering operation in the current chain and return the
1107
+ * set of matched elements to its previous state.
1108
+ *
1109
+ * @category Traversing
1110
+ * @example
1111
+ *
1112
+ * ```js
1113
+ * $('li').eq(0).end().length;
1114
+ * //=> 3
1115
+ * ```
1116
+ *
1117
+ * @returns The previous state of the set of matched elements.
1118
+ * @see {@link https://api.jquery.com/end/}
1119
+ */
1120
+ export function end<T>(this: Cheerio<T>): Cheerio<AnyNode> {
1121
+ return (this.prevObject as Cheerio<AnyNode> | null) ?? this._make([]);
1122
+ }
1123
+
1124
+ /**
1125
+ * Add elements to the set of matched elements.
1126
+ *
1127
+ * @category Traversing
1128
+ * @example
1129
+ *
1130
+ * ```js
1131
+ * $('.apple').add('.orange').length;
1132
+ * //=> 2
1133
+ * ```
1134
+ *
1135
+ * @param other - Elements to add.
1136
+ * @param context - Optionally the context of the new selection.
1137
+ * @returns The combined set.
1138
+ * @see {@link https://api.jquery.com/add/}
1139
+ */
1140
+ export function add<S extends AnyNode, T extends AnyNode>(
1141
+ this: Cheerio<T>,
1142
+ other: string | Cheerio<S> | S | S[],
1143
+ context?: Cheerio<S> | string,
1144
+ ): Cheerio<S | T> {
1145
+ const selection = this._make(other, context);
1146
+ const contents = uniqueSort([...this.get(), ...selection.get()]);
1147
+ return this._make(contents);
1148
+ }
1149
+
1150
+ /**
1151
+ * Add the previous set of elements on the stack to the current set, optionally
1152
+ * filtered by a selector.
1153
+ *
1154
+ * @category Traversing
1155
+ * @example
1156
+ *
1157
+ * ```js
1158
+ * $('li').eq(0).addBack('.orange').length;
1159
+ * //=> 2
1160
+ * ```
1161
+ *
1162
+ * @param selector - Selector for the elements to add.
1163
+ * @returns The combined set.
1164
+ * @see {@link https://api.jquery.com/addBack/}
1165
+ */
1166
+ export function addBack<T extends AnyNode>(
1167
+ this: Cheerio<T>,
1168
+ selector?: string,
1169
+ ): Cheerio<AnyNode> {
1170
+ return this.prevObject
1171
+ ? this.add<AnyNode, T>(
1172
+ selector ? this.prevObject.filter(selector) : this.prevObject,
1173
+ )
1174
+ : this;
1175
+ }