diff --git a/.eslintrc.json b/.eslintrc.json index 7dd762bd..b69c5c16 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -90,7 +90,8 @@ "extends": [ "plugin:expect-type/recommended", "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-type-checked", + "plugin:@typescript-eslint/stylistic-type-checked", "prettier" ], "parserOptions": { diff --git a/benchmark/benchmark.ts b/benchmark/benchmark.ts index bb2c81e4..5048f24d 100755 --- a/benchmark/benchmark.ts +++ b/benchmark/benchmark.ts @@ -18,12 +18,17 @@ const benchmarkFilter = filterIndex >= 0 ? process.argv[filterIndex] : ''; const cheerioOnly = process.argv.includes('--cheerio-only'); -interface SuiteOptions { - test($: CheerioAPI, data: T): void; - setup($: CheerioAPI): T; -} +type SuiteOptions = T extends void + ? { + test(this: void, $: CheerioAPI): void; + setup?: (this: void, $: CheerioAPI) => T; + } + : { + test(this: void, $: CheerioAPI, data: T): void; + setup(this: void, $: CheerioAPI): T; + }; -async function benchmark( +async function benchmark( name: string, fileName: string, options: SuiteOptions, @@ -40,7 +45,7 @@ async function benchmark( // Add Cheerio test const $ = load(markup); - const setupData: T = setup($); + const setupData = setup?.($) as T; bench.add('cheerio', () => { test($, setupData); @@ -52,23 +57,20 @@ async function benchmark( jQueryScript.runInContext(dom.getInternalVMContext()); - const setupData: T = setup(dom.window['$']); + const setupData = setup?.(dom.window['$'] as CheerioAPI) as T; - bench.add('jsdom', () => test(dom.window['$'], setupData)); + bench.add('jsdom', () => test(dom.window['$'] as CheerioAPI, setupData)); } - await bench.warmup(); // Make results more reliable, ref: https://github.com/tinylibs/tinybench/pull/50 await bench.run(); console.table(bench.table()); } -await benchmark('Select all', 'jquery.html', { - setup() {}, +await benchmark('Select all', 'jquery.html', { test: ($) => $('*').length, }); -await benchmark('Select some', 'jquery.html', { - setup() {}, +await benchmark('Select some', 'jquery.html', { test: ($) => $('li').length, }); @@ -116,7 +118,7 @@ await benchmark>('manipulation - remove', 'jquery.html', { }, }); -await benchmark('manipulation - replaceWith', 'jquery.html', { +await benchmark('manipulation - replaceWith', 'jquery.html', { setup($) { $('body').append('
'); }, @@ -147,8 +149,7 @@ await benchmark>('manipulation - html render', 'jquery.html', { const HTML_INDEPENDENT_MARKUP = '
bat
baz
'.repeat(6); -await benchmark('manipulation - html independent', 'jquery.html', { - setup() {}, +await benchmark('manipulation - html independent', 'jquery.html', { test: ($) => $(HTML_INDEPENDENT_MARKUP).html(), }); await benchmark>('manipulation - text', 'jquery.html', { diff --git a/package-lock.json b/package-lock.json index fcb1e74d..16503007 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,19 +35,19 @@ "eslint-plugin-expect-type": "^0.6.2", "eslint-plugin-jsdoc": "^50.6.1", "eslint-plugin-n": "^17.15.1", - "eslint-plugin-unicorn": "^55.0.0", + "eslint-plugin-unicorn": "^56.0.1", "eslint-plugin-vitest": "^0.5.4", "husky": "^9.1.7", "jquery": "^3.7.1", - "jsdom": "^24.1.1", + "jsdom": "^25.0.1", "lint-staged": "^15.2.11", "prettier": "^3.4.2", "prettier-plugin-jsdoc": "^1.3.0", - "tinybench": "^2.9.0", + "tinybench": "^3.1.0", "tshy": "^3.0.2", "tsx": "^4.19.2", - "typescript": "^5.5.4", - "vitest": "^2.0.5" + "typescript": "^5.7.2", + "vitest": "^2.1.8" }, "engines": { "node": ">=18.17" @@ -2282,9 +2282,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "dev": true, "funding": [ { @@ -2300,11 +2300,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -2344,9 +2345,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001609", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001609.tgz", - "integrity": "sha512-JFPQs34lHKx1B5t1EpQpWH4c+29zIyn/haGsbpfq3suuV9v56enjFt23zqijxGTMwy1p/4H2tjnQMY+p1WoAyA==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "dev": true, "funding": [ { @@ -2361,7 +2362,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chai": { "version": "5.1.2", @@ -2609,13 +2611,13 @@ "dev": true }, "node_modules/core-js-compat": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", - "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", + "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.23.0" + "browserslist": "^4.24.2" }, "funding": { "type": "opencollective", @@ -2623,10 +2625,11 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2672,12 +2675,13 @@ } }, "node_modules/cssstyle": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", - "integrity": "sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz", + "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==", "dev": true, + "license": "MIT", "dependencies": { - "rrweb-cssom": "^0.6.0" + "rrweb-cssom": "^0.7.1" }, "engines": { "node": ">=18" @@ -2862,10 +2866,11 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.736", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.736.tgz", - "integrity": "sha512-Rer6wc3ynLelKNM4lOCg7/zPQj8tPOCB2hzD32PX9wd3hgRRi9MxEbmkFCokzcEhRVMiOVLjnL9ig9cefJ+6+Q==", - "dev": true + "version": "1.5.75", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz", + "integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==", + "dev": true, + "license": "ISC" }, "node_modules/emoji-regex": { "version": "10.4.0", @@ -2979,10 +2984,11 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3214,18 +3220,19 @@ } }, "node_modules/eslint-plugin-unicorn": { - "version": "55.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-55.0.0.tgz", - "integrity": "sha512-n3AKiVpY2/uDcGrS3+QsYDkjPfaOrNrsfQxU9nt5nitd9KuvVXrfAvgCO9DYPSfap+Gqjw9EOrXIsBp5tlHZjA==", + "version": "56.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-56.0.1.tgz", + "integrity": "sha512-FwVV0Uwf8XPfVnKSGpMg7NtlZh0G0gBarCaFcMUOoqPxXryxdYxTRRv4kH6B9TFCVIrjRXG+emcxIk2ayZilog==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "@eslint-community/eslint-utils": "^4.4.0", "ci-info": "^4.0.0", "clean-regexp": "^1.0.0", - "core-js-compat": "^3.37.0", - "esquery": "^1.5.0", - "globals": "^15.7.0", + "core-js-compat": "^3.38.1", + "esquery": "^1.6.0", + "globals": "^15.9.0", "indent-string": "^4.0.0", "is-builtin-module": "^3.2.1", "jsesc": "^3.0.2", @@ -3233,7 +3240,7 @@ "read-pkg-up": "^7.0.1", "regexp-tree": "^0.1.27", "regjsparser": "^0.10.0", - "semver": "^7.6.1", + "semver": "^7.6.3", "strip-indent": "^3.0.0" }, "engines": { @@ -3247,10 +3254,11 @@ } }, "node_modules/eslint-plugin-unicorn/node_modules/globals": { - "version": "15.8.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.8.0.tgz", - "integrity": "sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -4120,12 +4128,13 @@ } }, "node_modules/jsdom": { - "version": "24.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.1.tgz", - "integrity": "sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==", + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz", + "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==", "dev": true, + "license": "MIT", "dependencies": { - "cssstyle": "^4.0.1", + "cssstyle": "^4.1.0", "data-urls": "^5.0.0", "decimal.js": "^10.4.3", "form-data": "^4.0.0", @@ -4138,7 +4147,7 @@ "rrweb-cssom": "^0.7.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.4", + "tough-cookie": "^5.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", "whatwg-encoding": "^3.1.1", @@ -4159,13 +4168,6 @@ } } }, - "node_modules/jsdom/node_modules/rrweb-cssom": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", - "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", - "dev": true, - "license": "MIT" - }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -5225,10 +5227,11 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" }, "node_modules/normalize-package-data": { "version": "2.5.0", @@ -5649,13 +5652,6 @@ "prettier": "^3.0.0" } }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true, - "license": "MIT" - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5665,13 +5661,6 @@ "node": ">=6" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -5837,13 +5826,6 @@ "jsesc": "bin/jsesc" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -6023,10 +6005,11 @@ } }, "node_modules/rrweb-cssom": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", - "integrity": "sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==", - "dev": true + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", + "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==", + "dev": true, + "license": "MIT" }, "node_modules/run-parallel": { "version": "1.2.0", @@ -6516,11 +6499,14 @@ "dev": true }, "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-3.1.0.tgz", + "integrity": "sha512-Km+oMh2xqNCxuyoUsqbRmHgFSd8sATh7v7xreP+kHN6x67w28Pawr83WmBxcaORvxkc0Ex6zgqK951yBnTFaaQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } }, "node_modules/tinyexec": { "version": "0.3.1", @@ -6556,6 +6542,26 @@ "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "6.1.69", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.69.tgz", + "integrity": "sha512-Oh/CqRQ1NXNY7cy9NkTPUauOWiTro0jEYZTioGbOmcQh6EC45oribyIMJp0OJO3677r13tO6SKdWoGZUx2BDFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.69" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.69", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.69.tgz", + "integrity": "sha512-nygxy9n2PBUFQUtAXAc122gGo+04/j5qr5TGQFZTHafTKYvmARVXt2cA5rgero2/dnXUfkdPtiJoKmrd3T+wdA==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6570,29 +6576,16 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" + "node": ">=16" } }, "node_modules/tr46": { @@ -7176,10 +7169,11 @@ } }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7238,9 +7232,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -7256,9 +7250,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -7276,17 +7271,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -7453,6 +7437,13 @@ } } }, + "node_modules/vitest/node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", diff --git a/package.json b/package.json index 172e4acd..d6e49483 100644 --- a/package.json +++ b/package.json @@ -141,19 +141,19 @@ "eslint-plugin-expect-type": "^0.6.2", "eslint-plugin-jsdoc": "^50.6.1", "eslint-plugin-n": "^17.15.1", - "eslint-plugin-unicorn": "^55.0.0", + "eslint-plugin-unicorn": "^56.0.1", "eslint-plugin-vitest": "^0.5.4", "husky": "^9.1.7", "jquery": "^3.7.1", - "jsdom": "^24.1.1", + "jsdom": "^25.0.1", "lint-staged": "^15.2.11", "prettier": "^3.4.2", "prettier-plugin-jsdoc": "^1.3.0", - "tinybench": "^2.9.0", + "tinybench": "^3.1.0", "tshy": "^3.0.2", "tsx": "^4.19.2", - "typescript": "^5.5.4", - "vitest": "^2.0.5" + "typescript": "^5.7.2", + "vitest": "^2.1.8" }, "engines": { "node": ">=18.17" diff --git a/src/api/attributes.ts b/src/api/attributes.ts index db246e62..47d0406f 100644 --- a/src/api/attributes.ts +++ b/src/api/attributes.ts @@ -9,7 +9,11 @@ import { domEach, camelCase, cssCase } from '../utils.js'; import { isTag, type AnyNode, type Element } from 'domhandler'; import type { Cheerio } from '../cheerio.js'; import { innerText, textContent } from 'domutils'; -const hasOwn = Object.prototype.hasOwnProperty; +const hasOwn = + // @ts-expect-error `hasOwn` is a standard object method + (Object.hasOwn as (object: unknown, prop: string) => boolean) ?? + ((object: unknown, prop: string) => + Object.prototype.hasOwnProperty.call(object, prop)); const rspace = /\s+/; const dataAttrPrefix = 'data-'; @@ -56,7 +60,7 @@ function getAttr( return elem.attribs; } - if (hasOwn.call(elem.attribs, name)) { + if (hasOwn(elem.attribs, name)) { // Get the (decoded) attribute return !xmlMode && rboolean.test(name) ? name : elem.attribs[name]; } @@ -209,14 +213,14 @@ export function attr( setAttr(el, objName, objValue); } } else { - setAttr(el, name as string, value as string); + setAttr(el, name!, value!); } }); } return arguments.length > 1 ? this - : getAttr(this[0], name as string, this.options.xmlMode); + : getAttr(this[0], name!, this.options.xmlMode); } /** @@ -233,10 +237,10 @@ function getProp( el: Element, name: string, xmlMode?: boolean, -): string | undefined | Element[keyof Element] { +): string | undefined | boolean | Element[keyof Element] { return name in el ? // @ts-expect-error TS doesn't like us accessing the value directly here. - el[name] + (el[name] as string | undefined) : !xmlMode && rboolean.test(name) ? getAttr(el, name, false) !== undefined : getAttr(el, name, xmlMode); @@ -259,7 +263,11 @@ function setProp(el: Element, name: string, value: unknown, xmlMode?: boolean) { setAttr( el, name, - !xmlMode && rboolean.test(name) ? (value ? '' : null) : `${value}`, + !xmlMode && rboolean.test(name) + ? value + ? '' + : null + : `${value as string}`, ); } } @@ -396,14 +404,15 @@ export function prop(this: Cheerio, name: string): string; export function prop( this: Cheerio, name: string | Record, - value?: - | (( - this: Element, - i: number, - prop: string | undefined, - ) => string | Element[keyof Element] | boolean) - | unknown, -): Cheerio | string | undefined | null | Element[keyof Element] | StyleProp { + value?: unknown, +): + | Cheerio + | string + | boolean + | undefined + | null + | Element[keyof Element] + | StyleProp { if (typeof name === 'string' && value === undefined) { const el = this[0]; @@ -552,7 +561,7 @@ function readAllData(el: DataElement): unknown { const jsName = camelCase(domName.slice(dataAttrPrefix.length)); - if (!hasOwn.call(el.data, jsName)) { + if (!hasOwn(el.data, jsName)) { el.data![jsName] = parseDataValue(el.attribs[domName]); } } @@ -574,11 +583,11 @@ function readData(el: DataElement, name: string): unknown { const domName = dataAttrPrefix + cssCase(name); const data = el.data!; - if (hasOwn.call(data, name)) { + if (hasOwn(data, name)) { return data[name]; } - if (hasOwn.call(el.attribs, domName)) { + if (hasOwn(el.attribs, domName)) { return (data[name] = parseDataValue(el.attribs[domName])); } @@ -629,7 +638,7 @@ function parseDataValue(value: string): unknown { export function data( this: Cheerio, name: string, -): unknown | undefined; +): unknown; /** * Method for getting all of an element's data attributes, for only the first * element in the matched set. @@ -698,7 +707,7 @@ export function data( this: Cheerio, name?: string | Record, value?: unknown, -): unknown | Cheerio | undefined | Record { +): unknown { const elem = this[0]; if (!elem || !isTag(elem)) return; @@ -716,7 +725,7 @@ export function data( domEach(this, (el) => { if (isTag(el)) { if (typeof name === 'object') setData(el, name); - else setData(el, name, value as unknown); + else setData(el, name, value); } }); return this; @@ -816,7 +825,7 @@ export function val( * @param name - Name of the attribute to remove. */ function removeAttribute(elem: Element, name: string) { - if (!elem.attribs || !hasOwn.call(elem.attribs, name)) return; + if (!elem.attribs || !hasOwn(elem.attribs, name)) return; delete elem.attribs[name]; } @@ -1030,7 +1039,7 @@ export function removeClass>( for (let j = 0; j < numClasses; j++) { const index = elClasses.indexOf(classes[j]); - if (index >= 0) { + if (index !== -1) { elClasses.splice(index, 1); changed = true; @@ -1115,9 +1124,9 @@ export function toggleClass>( const index = elementClasses.indexOf(classNames[j]); // Add if stateValue === true or we are toggling and there is no value - if (state >= 0 && index < 0) { + if (state >= 0 && index === -1) { elementClasses.push(classNames[j]); - } else if (state <= 0 && index >= 0) { + } else if (state <= 0 && index !== -1) { // Otherwise remove but only if the item exists elementClasses.splice(index, 1); } diff --git a/src/api/extract.ts b/src/api/extract.ts index 10bd529c..8bb0b62c 100644 --- a/src/api/extract.ts +++ b/src/api/extract.ts @@ -16,9 +16,7 @@ interface ExtractDescriptor { type ExtractValue = string | ExtractDescriptor | [string | ExtractDescriptor]; -export interface ExtractMap { - [key: string]: ExtractValue; -} +export type ExtractMap = Record; type ExtractedValue = V extends [ string | ExtractDescriptor, diff --git a/src/api/forms.ts b/src/api/forms.ts index 902f9196..a9faf857 100644 --- a/src/api/forms.ts +++ b/src/api/forms.ts @@ -82,7 +82,7 @@ export function serializeArray( } >((_, elem) => { const $elem = this._make(elem); - const name = $elem.attr('name') as string; // We have filtered for elements with a name before. + const name = $elem.attr('name')!; // We have filtered for elements with a name before. // If there is no value set (e.g. `undefined`, `null`), then default value to empty const value = $elem.val() ?? ''; diff --git a/src/api/manipulation.ts b/src/api/manipulation.ts index 7c76360b..07a4b517 100644 --- a/src/api/manipulation.ts +++ b/src/api/manipulation.ts @@ -20,6 +20,7 @@ import { domEach, isHtml, isCheerio } from '../utils.js'; import { removeElement } from 'domutils'; import type { Cheerio } from '../cheerio.js'; import type { BasicAcceptedElems, AcceptedElems } from '../types.js'; +import { ElementType } from 'htmlparser2'; /** * Create an array of nodes, recursing into arrays and parsing strings if @@ -129,7 +130,7 @@ function uniqueSplice( newElems: AnyNode[], parent: ParentNode, ): AnyNode[] { - const spliceArgs: Parameters = [ + const spliceArgs: Parameters = [ spliceIdx, spliceCount, ...newElems, @@ -152,7 +153,7 @@ function uniqueSplice( const oldSiblings: AnyNode[] = oldParent.children; const prevIdx = oldSiblings.indexOf(node); - if (prevIdx > -1) { + if (prevIdx !== -1) { oldParent.children.splice(prevIdx, 1); if (parent === oldParent && spliceIdx > prevIdx) { spliceArgs[0]--; @@ -588,7 +589,9 @@ export function wrapAll( let elInsertLocation: Element | undefined; for (let i = 0; i < wrap.length; i++) { - if (wrap[i].type === 'tag') elInsertLocation = wrap[i] as Element; + if (wrap[i].type === ElementType.Tag) { + elInsertLocation = wrap[i] as Element; + } } let j = 0; @@ -599,8 +602,8 @@ export function wrapAll( */ while (elInsertLocation && j < elInsertLocation.children.length) { const child = elInsertLocation.children[j]; - if (child.type === 'tag') { - elInsertLocation = child as Element; + if (child.type === ElementType.Tag) { + elInsertLocation = child; j = 0; } else { j++; @@ -652,7 +655,7 @@ export function after( // If not found, move on /* istanbul ignore next */ - if (index < 0) return; + if (index === -1) return; const domSrc = typeof elems[0] === 'function' @@ -711,7 +714,7 @@ export function insertAfter( // If not found, move on /* istanbul ignore next */ - if (index < 0) continue; + if (index === -1) continue; // Add cloned `this` element(s) after target element uniqueSplice(siblings, index + 1, 0, clonedSelf, parent); @@ -761,7 +764,7 @@ export function before( // If not found, move on /* istanbul ignore next */ - if (index < 0) return; + if (index === -1) return; const domSrc = typeof elems[0] === 'function' @@ -818,7 +821,7 @@ export function insertBefore( // If not found, move on /* istanbul ignore next */ - if (index < 0) return; + if (index === -1) return; // Add cloned `this` element(s) after target element uniqueSplice(siblings, index, 0, clonedSelf, parent); @@ -1097,8 +1100,9 @@ export function text( * @see {@link https://api.jquery.com/clone/} */ export function clone(this: Cheerio): Cheerio { - const clone = Array.prototype.map.call(this.get(), (el) => - cloneNode(el, true), + const clone = Array.prototype.map.call( + this.get(), + (el) => cloneNode(el, true) as T, ) as T[]; // Add a root node around the cloned nodes diff --git a/src/api/traversing.spec.ts b/src/api/traversing.spec.ts index b57baf6b..2784efab 100644 --- a/src/api/traversing.spec.ts +++ b/src/api/traversing.spec.ts @@ -30,7 +30,8 @@ describe('$(...)', () => { describe('.load', () => { it('should throw a TypeError if given invalid input', () => { expect(() => { - (load as any)(); + // @ts-expect-error Testing invalid input + load(); }).toThrow('cheerio.load() expects a string'); }); }); @@ -858,7 +859,7 @@ describe('$(...)', () => { it('should yield each element', () => { // The equivalent of: for (const element of $('li')) ... const $li = $('li'); - const iterator = $li[Symbol.iterator](); + const iterator = $li[Symbol.iterator]() as Iterator; expect(iterator.next().value.attribs).toHaveProperty('class', 'apple'); expect(iterator.next().value.attribs).toHaveProperty('class', 'orange'); expect(iterator.next().value.attribs).toHaveProperty('class', 'pear'); diff --git a/src/api/traversing.ts b/src/api/traversing.ts index cae098a7..8a853f73 100644 --- a/src/api/traversing.ts +++ b/src/api/traversing.ts @@ -1025,7 +1025,7 @@ export function get(this: Cheerio, i?: number): T | T[] { * @returns The contained items. */ export function toArray(this: Cheerio): T[] { - return Array.prototype.slice.call(this); + return (Array.prototype as T[]).slice.call(this); } /** @@ -1097,7 +1097,7 @@ export function slice( start?: number, end?: number, ): Cheerio { - return this._make(Array.prototype.slice.call(this, start, end)); + return this._make(Array.prototype.slice.call(this, start, end)); } /** @@ -1116,7 +1116,7 @@ export function slice( * @see {@link https://api.jquery.com/end/} */ export function end(this: Cheerio): Cheerio { - return this.prevObject ?? this._make([]); + return (this.prevObject as Cheerio | null) ?? this._make([]); } /** @@ -1166,6 +1166,8 @@ export function addBack( selector?: string, ): Cheerio { return this.prevObject - ? this.add(selector ? this.prevObject.filter(selector) : this.prevObject) + ? this.add( + selector ? this.prevObject.filter(selector) : this.prevObject, + ) : this; } diff --git a/src/cheerio.spec.ts b/src/cheerio.spec.ts index 380c80ef..d9ff1c5f 100644 --- a/src/cheerio.spec.ts +++ b/src/cheerio.spec.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; import { parseDOM } from 'htmlparser2'; import { type Cheerio } from './index.js'; import { cheerio, fruits, food, noscript } from './__fixtures__/fixtures.js'; -import type { Element } from 'domhandler'; +import type { AnyNode, Element } from 'domhandler'; declare module './index.js' { interface Cheerio { @@ -10,7 +10,7 @@ declare module './index.js' { context: Cheerio; args: unknown[]; }; - foo(): void; + foo(this: void): void; } } @@ -221,17 +221,23 @@ describe('cheerio', () => { }); it('(extended Array) should not interfere with prototype methods (issue #119)', () => { - const extended: any = []; + const extended: AnyNode[] = []; + // @ts-expect-error - Ignore for testing extended.find = + // @ts-expect-error - Ignore for testing extended.children = + // @ts-expect-error - Ignore for testing extended.each = function () { /* Ignore */ }; const $empty = cheerio(extended); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access expect($empty.find).toBe(cheerio.prototype.find); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access expect($empty.children).toBe(cheerio.prototype.children); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access expect($empty.each).toBe(cheerio.prototype.each); }); @@ -360,7 +366,9 @@ describe('cheerio', () => { it('should honor extensions defined on `prototype` property', () => { const $ = cheerio.load('
'); - $.prototype.myPlugin = function (...args: unknown[]) { + ($.prototype as Cheerio).myPlugin = function ( + ...args: unknown[] + ) { return { context: this, args, @@ -394,7 +402,7 @@ describe('cheerio', () => { const $a = cheerio.load('
'); const $b = cheerio.load('
'); - $a.prototype.foo = function () { + ($a.prototype as Cheerio).foo = function () { /* Ignore */ }; diff --git a/src/index.spec.ts b/src/index.spec.ts index e1e9a733..11091e5b 100644 --- a/src/index.spec.ts +++ b/src/index.spec.ts @@ -9,12 +9,12 @@ function noop() { // Returns a promise and a resolve function function getPromise() { - let cb: (error: Error | null | undefined, $: cheerio.CheerioAPI) => void; + let cb!: (error: Error | null | undefined, $: cheerio.CheerioAPI) => void; const promise = new Promise((resolve, reject) => { cb = (error, $) => (error ? reject(error) : resolve($)); }); - return { promise, cb: cb! }; + return { promise, cb }; } const TEST_HTML = '

Hello World

'; diff --git a/src/index.ts b/src/index.ts index 681a3113..49789d26 100644 --- a/src/index.ts +++ b/src/index.ts @@ -228,10 +228,11 @@ export async function fromURL( const promise = new Promise((resolve, reject) => { undiciStream = undici.stream(url, requestOptions, (res) => { - const contentType = res.headers['content-type'] ?? 'text/html'; - const mimeType = new MIMEType( - Array.isArray(contentType) ? contentType[0] : contentType, - ); + const contentTypeHeader = res.headers['content-type'] ?? 'text/html'; + const contentType = Array.isArray(contentTypeHeader) + ? contentTypeHeader[0] + : contentTypeHeader; + const mimeType = new MIMEType(contentType); if (!mimeType.isHTML() && !mimeType.isXML()) { throw new RangeError( diff --git a/src/load.ts b/src/load.ts index 85c60406..75e2827c 100644 --- a/src/load.ts +++ b/src/load.ts @@ -8,6 +8,7 @@ import { Cheerio } from './cheerio.js'; import { isHtml, isCheerio } from './utils.js'; import type { AnyNode, Document, Element, ParentNode } from 'domhandler'; import type { SelectorType, BasicAcceptedElems } from './types.js'; +import { ElementType } from 'htmlparser2'; type StaticType = typeof staticMethods; @@ -104,7 +105,7 @@ export interface CheerioAPI extends StaticType { } export function getLoad( - parse: typeof Cheerio.prototype._parse, + parse: Cheerio['_parse'], render: ( dom: AnyNode | ArrayLike, options: InternalOptions, @@ -209,7 +210,7 @@ export function getLoad( const instance = new LoadedCheerio(elements, rootInstance, options); if (elements) { - return instance as any; + return instance as Cheerio; } if (typeof selector !== 'string') { @@ -243,7 +244,7 @@ export function getLoad( : rootInstance; // If we still don't have a context, return - if (!searchContext) return instance as any; + if (!searchContext) return instance as Cheerio; /* * #id, .class, tag @@ -267,11 +268,15 @@ export function getLoad( }; } -function isNode(obj: any): obj is AnyNode { +function isNode(obj: unknown): obj is AnyNode { return ( + // @ts-expect-error: TS doesn't know about the `name` property. !!obj.name || - obj.type === 'root' || - obj.type === 'text' || - obj.type === 'comment' + // @ts-expect-error: TS doesn't know about the `type` property. + obj.type === ElementType.Root || + // @ts-expect-error: TS doesn't know about the `type` property. + obj.type === ElementType.Text || + // @ts-expect-error: TS doesn't know about the `type` property. + obj.type === ElementType.Comment ); } diff --git a/src/parse.spec.ts b/src/parse.spec.ts index f5dc3dfb..09cdc3e5 100644 --- a/src/parse.spec.ts +++ b/src/parse.spec.ts @@ -264,7 +264,7 @@ describe('parse', () => { expect(childNodes[0].previousSibling).toBe(null); expect(childNodes[0].nextSibling).toBe(childNodes[1]); expect(childNodes[0].parentNode).toBe(root); - expect((childNodes[0] as Element).childNodes).toHaveLength(0); + expect(childNodes[0].childNodes).toHaveLength(0); expect(childNodes[0].firstChild).toBe(null); expect(childNodes[0].lastChild).toBe(null); @@ -335,23 +335,17 @@ describe('parse', () => { false, null, ); - const childNodes = root.childNodes as Element[]; - expect(childNodes[0].tagName).toBe('table'); - expect(childNodes[0].childNodes.length).toBe(1); - expect(childNodes[0].childNodes[0]).toHaveProperty('tagName', 'tbody'); - expect((childNodes[0] as any).childNodes[0].childNodes[0]).toHaveProperty( - 'tagName', - 'tr', - ); - expect( - (childNodes[0] as any).childNodes[0].childNodes[0].childNodes[0] - .tagName, - ).toBe('td'); - expect( - (childNodes[0] as any).childNodes[0].childNodes[0].childNodes[0] - .childNodes[0].data, - ).toBe('bar'); + const table = root.childNodes[0] as Element; + expect(table.tagName).toBe('table'); + expect(table.childNodes.length).toBe(1); + const tbody = table.childNodes[0] as Element; + expect(table.childNodes[0]).toHaveProperty('tagName', 'tbody'); + const tr = tbody.childNodes[0] as Element; + expect(tr).toHaveProperty('tagName', 'tr'); + const td = tr.childNodes[0] as Element; + expect(td).toHaveProperty('tagName', 'td'); + expect(td.childNodes[0]).toHaveProperty('data', 'bar'); }); it('Should parse custom tag ', () => { diff --git a/src/static.ts b/src/static.ts index b26f2138..3d93d4e9 100644 --- a/src/static.ts +++ b/src/static.ts @@ -152,14 +152,14 @@ export function text( export function parseHTML( this: CheerioAPI, data: string, - context?: unknown | boolean, + context?: unknown, keepScripts?: boolean, ): AnyNode[]; export function parseHTML(this: CheerioAPI, data?: '' | null): null; export function parseHTML( this: CheerioAPI, data?: string | null, - context?: unknown | boolean, + context?: unknown, keepScripts = typeof context === 'boolean' ? context : false, ): AnyNode[] | null { if (!data || typeof data !== 'string') { diff --git a/src/utils.ts b/src/utils.ts index c10efb7d..f5be2f1b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -8,8 +8,10 @@ import type { Cheerio } from './cheerio.js'; * @param maybeCheerio - The object to check. * @returns Whether the object is a Cheerio instance. */ -export function isCheerio(maybeCheerio: any): maybeCheerio is Cheerio { - return maybeCheerio.cheerio != null; +export function isCheerio( + maybeCheerio: unknown, +): maybeCheerio is Cheerio { + return (maybeCheerio as Cheerio).cheerio != null; } /** @@ -21,7 +23,7 @@ export function isCheerio(maybeCheerio: any): maybeCheerio is Cheerio { * @returns String in camel case notation. */ export function camelCase(str: string): string { - return str.replace(/[._-](\w|$)/g, (_, x) => x.toUpperCase()); + return str.replace(/[._-](\w|$)/g, (_, x) => (x as string).toUpperCase()); } /** @@ -58,7 +60,7 @@ export function domEach< return array; } -const enum CharacterCodes { +const enum CharacterCode { LowerA = 97, LowerZ = 122, UpperA = 65, @@ -80,14 +82,14 @@ const enum CharacterCodes { export function isHtml(str: string): boolean { const tagStart = str.indexOf('<'); - if (tagStart < 0 || tagStart > str.length - 3) return false; + if (tagStart === -1 || tagStart > str.length - 3) return false; - const tagChar = str.charCodeAt(tagStart + 1); + const tagChar = str.charCodeAt(tagStart + 1) as CharacterCode; return ( - ((tagChar >= CharacterCodes.LowerA && tagChar <= CharacterCodes.LowerZ) || - (tagChar >= CharacterCodes.UpperA && tagChar <= CharacterCodes.UpperZ) || - tagChar === CharacterCodes.Exclamation) && + ((tagChar >= CharacterCode.LowerA && tagChar <= CharacterCode.LowerZ) || + (tagChar >= CharacterCode.UpperA && tagChar <= CharacterCode.UpperZ) || + tagChar === CharacterCode.Exclamation) && str.includes('>', tagStart + 2) ); } diff --git a/vitest.config.ts b/vitest.config.ts index 88e55a29..8b6dab26 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,6 +1,6 @@ -import { defineConfig } from 'vitest/config'; +import { defineConfig, type ViteUserConfig } from 'vitest/config'; -export default defineConfig({ +const config: ViteUserConfig = defineConfig({ test: { coverage: { exclude: [ @@ -13,3 +13,5 @@ export default defineConfig({ }, }, }); + +export default config;