diff --git a/.github/workflows/nodejs-test.yml b/.github/workflows/nodejs-test.yml index bb5e4cc4..79262bd9 100644 --- a/.github/workflows/nodejs-test.yml +++ b/.github/workflows/nodejs-test.yml @@ -36,6 +36,7 @@ jobs: - 20 - 22 - 24 + - 26 - lts/* steps: diff --git a/eslint.config.mjs b/eslint.config.mjs index e4aa5d7b..944f128b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -47,4 +47,10 @@ export default defineConfig([ }, }, eslintConfigBiome, + { + rules: { + // This module uses parser DOM nodes, which do not implement browser traversal APIs. + "unicorn/better-dom-traversing": "off", + }, + }, ]); diff --git a/package-lock.json b/package-lock.json index 1b957b55..4980ec3d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "devDependencies": { "@biomejs/biome": "^2.5.15", "@eslint/compat": "^2.1.1", - "@feedic/eslint-config": "^0.4.1", + "@feedic/eslint-config": "^0.5.0", "@types/node": "^26.6.4", "eslint": "^10.12.0", "eslint-config-biome": "^2.1.3", @@ -37,16 +37,16 @@ } }, "node_modules/@altano/repository-tools": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@altano/repository-tools/-/repository-tools-2.0.3.tgz", - "integrity": "sha512-cSR/ZYDF6Wp9OeAJMyLYYN1GenAAhV17W+w38ELP+3c5Ltsy9jkkCymi33nz/qnXyef3n6Fbr1h2yt3dvUN5sQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@altano/repository-tools/-/repository-tools-2.1.1.tgz", + "integrity": "sha512-YFVhwoSjwkERQoopw3lX/TaPIDud0m6ac/Z51FoQ3YEPf0OaxFxpofbcHO0E2mTJ0lvmuRArIMUP9Pz2jpTMhg==", "dev": true, "license": "ISC" }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -253,17 +253,17 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.86.0.tgz", - "integrity": "sha512-ukZmRQ81WiTpDWO6D/cTBM7XbrNtutHKvAVnZN/8pldAwLoJArGOvkNyxPTBGsPjsoaQBJxlH+tE2TNA/92Qgw==", + "version": "0.91.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.91.0.tgz", + "integrity": "sha512-vgqlMGNNhZxwDYbUNIHj3Hskb4R28iqdXx90ufHyt/NeuTQkeqjTDslAs9I0/GCAfbxP5BpH5WsL1R1fht5Lxg==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.8", - "@typescript-eslint/types": "^8.58.0", - "comment-parser": "1.4.6", + "@types/estree": "^1.0.9", + "@typescript-eslint/types": "^8.65.0", + "comment-parser": "1.4.7", "esquery": "^1.7.0", - "jsdoc-type-pratt-parser": "~7.2.0" + "jsdoc-type-pratt-parser": "~8.0.0" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -429,18 +429,18 @@ } }, "node_modules/@feedic/eslint-config": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@feedic/eslint-config/-/eslint-config-0.4.1.tgz", - "integrity": "sha512-F6zETaaeGbsbHy8mqfQgVoNVIkneqd2NAtQkl4PSajVRcwTQ4D+UqrKVlF6yUpn6G0wudjS5gp5WloW+6hig/A==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@feedic/eslint-config/-/eslint-config-0.5.0.tgz", + "integrity": "sha512-j3rj0vDAGJ/FpgYKiHgrknWx2vEzdIvTcE6/jCWsYmCI8YV6Mpkn9gzpmkBsomQ4XgiDXD6nQeaz0m83I9oGog==", "dev": true, "license": "MIT", "dependencies": { "@eslint/js": "^10.0.1", - "eslint-plugin-jsdoc": "^62.9.0", - "eslint-plugin-n": "^17.24.0", - "eslint-plugin-package-json": "^0.91.2", - "eslint-plugin-unicorn": "^64.0.0", - "globals": "^17.6.0" + "eslint-plugin-jsdoc": "^63.0.7", + "eslint-plugin-n": "^18.1.0", + "eslint-plugin-package-json": "^1.5.0", + "eslint-plugin-unicorn": "^68.0.0", + "globals": "^17.7.0" }, "engines": { "node": ">=20.19.0" @@ -913,9 +913,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -1254,6 +1254,13 @@ "node": ">=14" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1275,9 +1282,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.11.20", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", - "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "version": "2.11.28", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.28.tgz", + "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1314,9 +1321,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", - "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "version": "4.29.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz", + "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==", "dev": true, "funding": [ { @@ -1334,11 +1341,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.11.12", - "caniuse-lite": "^1.0.30001809", - "electron-to-chromium": "^1.5.402", - "node-releases": "^2.0.53", - "update-browserslist-db": "^1.3.0" + "baseline-browser-mapping": "^2.11.26", + "caniuse-lite": "^1.0.30001813", + "electron-to-chromium": "^1.5.439", + "node-releases": "^2.0.57", + "update-browserslist-db": "^1.3.3" }, "bin": { "browserslist": "cli.js" @@ -1348,9 +1355,9 @@ } }, "node_modules/builtin-modules": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.1.0.tgz", - "integrity": "sha512-c5JxaDrzwRjq3WyJkI1AGR5xy6Gr6udlt7sQPbl09+3ckB+Zo2qqQ2KhCTBr7Q8dHB43bENGYEk4xddrFH/b7A==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.4.0.tgz", + "integrity": "sha512-JCWSCdun+4Ovd9BhM/Vtlmwb7oD6Wl4YiPRXXwhAuwlPhW1un/MKgXn7GZoFm53ginnoNzus69V8JWx0K393jg==", "dev": true, "license": "MIT", "engines": { @@ -1375,9 +1382,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", - "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "version": "1.0.30001815", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz", + "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==", "dev": true, "funding": [ { @@ -1428,33 +1435,10 @@ "node": ">=8" } }, - "node_modules/clean-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", - "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/clean-regexp/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/comment-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.6.tgz", - "integrity": "sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.7.tgz", + "integrity": "sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==", "dev": true, "license": "MIT", "engines": { @@ -1462,13 +1446,16 @@ } }, "node_modules/core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "version": "3.50.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", + "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.28.1" + "browserslist": "^4.28.7" + }, + "engines": { + "node": ">=6.4.0" }, "funding": { "type": "opencollective", @@ -1633,21 +1620,21 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.420", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.420.tgz", - "integrity": "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA==", + "version": "1.5.452", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.452.tgz", + "integrity": "sha512-cVvk1YBSj6571VmV6hdJTqj94Y79xSu8cj6Y9t6ae4Y4431z8/7EJFvu3rVp90xhfuv9BpPKUmaLfTo4+FOGBg==", "dev": true, "license": "ISC" }, "node_modules/enhanced-resolve": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", - "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz", + "integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -1778,9 +1765,9 @@ "license": "MIT" }, "node_modules/eslint-fix-utils": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/eslint-fix-utils/-/eslint-fix-utils-0.4.2.tgz", - "integrity": "sha512-n7ZTcwwkP5scedlhvWMcqxED+O1NzXcj5Rxn/0kJQMP88k02vRcBfQ1qsk/JHb6Aw8bajFoetFCCBiNIcNCsvA==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/eslint-fix-utils/-/eslint-fix-utils-0.4.4.tgz", + "integrity": "sha512-9reP0fw++eZv0qs0ji8U1G9a+j8yWQW1RT8Wrlw32JlhQJDiweVX98kyDrJq6G7jdOLgKwmj0nPghyMadsOgHQ==", "dev": true, "license": "MIT", "engines": { @@ -1796,6 +1783,28 @@ } } }, + "node_modules/eslint-json-compat-utils": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/eslint-json-compat-utils/-/eslint-json-compat-utils-0.2.3.tgz", + "integrity": "sha512-RbBmDFyu7FqnjE8F0ZxPNzx5UaptdeS9Uu50r7A+D7s/+FCX+ybiyViYEgFUaFIFqSWJgZRTpL5d8Kanxxl2lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esquery": "^1.6.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": "*", + "jsonc-eslint-parser": "^2.4.0 || ^3.0.0" + }, + "peerDependenciesMeta": { + "@eslint/json": { + "optional": true + } + } + }, "node_modules/eslint-plugin-es-x": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", @@ -1819,38 +1828,38 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "62.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.9.0.tgz", - "integrity": "sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==", + "version": "63.3.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.3.3.tgz", + "integrity": "sha512-xI4IeVRzRFA2DGHrPLIxF3U+oJHU3FE+P9Zb27fVs5dPHgfcpoAs0PyCbznVhK7pwR+9BPUztFeXSgpw/CL4Yg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@es-joy/jsdoccomment": "~0.86.0", + "@es-joy/jsdoccomment": "~0.91.0", "@es-joy/resolve.exports": "1.2.0", "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.6", + "comment-parser": "1.4.7", "debug": "^4.4.3", "escape-string-regexp": "^4.0.0", "espree": "^11.2.0", "esquery": "^1.7.0", "html-entities": "^2.6.0", - "object-deep-merge": "^2.0.0", + "object-deep-merge": "^2.0.1", "parse-imports-exports": "^0.2.4", - "semver": "^7.7.4", - "spdx-expression-parse": "^4.0.0", + "semver": "^7.8.5", + "spdx-expression-parse": "^5.0.0", "to-valid-identifier": "^1.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": "^22.13.0 || >=24" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-plugin-n": { - "version": "17.24.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.24.0.tgz", - "integrity": "sha512-/gC7/KAYmfNnPNOb3eu8vw+TdVnV0zhdQwexsw6FLXbhzroVj20vRn2qL8lDWDGnAQ2J8DhdfvXxX9EoxvERvw==", + "version": "18.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.4.1.tgz", + "integrity": "sha512-2sBReWOvCbzgkPri9M5SFmD5EbIwZLa1xz6B5RZsrI19WSFKpyx1sOMr+pdk8puCiXuEgCbErEHy1PuaUrc/4w==", "dev": true, "license": "MIT", "dependencies": { @@ -1861,17 +1870,27 @@ "globals": "^15.11.0", "globrex": "^0.1.2", "ignore": "^5.3.2", - "semver": "^7.6.3", - "ts-declaration-location": "^1.0.6" + "js-yaml": "^5.4.1", + "semver": "^7.6.3" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "eslint": ">=8.23.0" + "eslint": ">=8.57.1", + "ts-declaration-location": "^1.0.6", + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "ts-declaration-location": { + "optional": true + }, + "typescript": { + "optional": true + } } }, "node_modules/eslint-plugin-n/node_modules/globals": { @@ -1888,63 +1907,70 @@ } }, "node_modules/eslint-plugin-package-json": { - "version": "0.91.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-package-json/-/eslint-plugin-package-json-0.91.2.tgz", - "integrity": "sha512-tuPjHVYOjqEJtErmzuYiQY6o877l9Kb7+lfLhR/mAzHuy9CBqhRreIGPsQmVM/dMJ8yQVg92Bz1vBAgugELIXw==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-package-json/-/eslint-plugin-package-json-1.10.1.tgz", + "integrity": "sha512-H/NhssfjCi0PjypHab8R1bM2NPn9FASn/MBrVf8+CmykFM+wg9Wz4XgWgtHRpbXtm3R4S5nJv0HXYeucpPRJpA==", "dev": true, "license": "MIT", "dependencies": { "@altano/repository-tools": "^2.0.1", + "@types/estree": "^1.0.0", "change-case": "^5.4.4", "detect-indent": "^7.0.2", "detect-newline": "^4.0.1", - "eslint-fix-utils": "~0.4.1", - "package-json-validator": "^1.4.1", + "eslint-fix-utils": "~0.4.3", + "eslint-json-compat-utils": "^0.2.3", + "jsonc-eslint-parser": "^3.1.0", + "package-json-validator": "^1.6.0", "semver": "^7.7.3", "sort-object-keys": "^2.0.0", - "sort-package-json": "^3.4.0", - "validate-npm-package-name": "^7.0.0" + "sort-package-json": "^4.0.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": "^22.22.2 || >=24.15.0" }, "peerDependencies": { - "eslint": ">=8.0.0", - "jsonc-eslint-parser": ">=2.0.0" + "@eslint/json": ">=1.0.0", + "eslint": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@eslint/json": { + "optional": true + } } }, "node_modules/eslint-plugin-unicorn": { - "version": "64.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-64.0.0.tgz", - "integrity": "sha512-rNZwalHh8i0UfPlhNwg5BTUO1CMdKNmjqe+TgzOTZnpKoi8VBgsW7u9qCHIdpxEzZ1uwrJrPF0uRb7l//K38gA==", + "version": "68.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-68.0.0.tgz", + "integrity": "sha512-mHYWvX948Q4H3bGc39bsNMxD/leOuNI+Iws9NVsoSz5VA7EGP86wnz7mZ/SPSvRhefT8L4hd8DHfDuGC+lIoCQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "@eslint-community/eslint-utils": "^4.9.1", + "browserslist": "^4.28.2", "change-case": "^5.4.4", "ci-info": "^4.4.0", - "clean-regexp": "^1.0.0", "core-js-compat": "^3.49.0", + "detect-indent": "^7.0.2", "find-up-simple": "^1.0.1", - "globals": "^17.4.0", + "globals": "^17.6.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "jsesc": "^3.1.0", "pluralize": "^8.0.0", - "regexp-tree": "^0.1.27", - "regjsparser": "^0.13.0", - "semver": "^7.7.4", + "regjsparser": "^0.13.1", + "semver": "^7.8.4", "strip-indent": "^4.1.1" }, "engines": { - "node": "^20.10.0 || >=21.0.0" + "node": ">=22" }, "funding": { "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" }, "peerDependencies": { - "eslint": ">=9.38.0" + "eslint": ">=10.4" } }, "node_modules/eslint-scope": { @@ -2178,9 +2204,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -2405,10 +2431,33 @@ "dev": true, "license": "ISC" }, + "node_modules/js-yaml": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.3.tgz", + "integrity": "sha512-yzzNtgczwgoVHNigCyKTXcQJU4Y8t/ikU4UxXW0BvrLfHTGWZhsNcOT2s/a7vn81/4KqVnNmHjaUEPlvWnNwVw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.mjs" + } + }, "node_modules/jsdoc-type-pratt-parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.2.0.tgz", - "integrity": "sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-8.0.0.tgz", + "integrity": "sha512-uQu/fXVqVaMg6gM8/E5G5+eygVcZ1NV0Z51CvqhNa2bDWxvHMl484ETr6vph4oPyC+KUcbP/w2W2pewfCiR9aQ==", "dev": true, "license": "MIT", "engines": { @@ -2443,16 +2492,15 @@ "license": "MIT" }, "node_modules/jsonc-eslint-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-3.1.0.tgz", - "integrity": "sha512-75EA7EWZExL/j+MDKQrRbdzcRI2HOkRlmUw8fZJc1ioqFEOvBsq7Rt+A6yCxOt9w/TYNpkt52gC6nm/g5tFIng==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/jsonc-eslint-parser/-/jsonc-eslint-parser-3.3.0.tgz", + "integrity": "sha512-hYTGkHGNRZnXOFZ1urhINADoqDrGfpy53cjw+dxk84QE0pUDujQzeUeamNs6Mz44/TKD49z2x6/GVSu4ZrtA+Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^5.0.0", - "semver": "^7.3.5" + "verkit": "^0.3.2" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -2787,9 +2835,9 @@ } }, "node_modules/lru-cache": { - "version": "11.3.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", - "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -2857,9 +2905,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.54", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", - "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "version": "2.0.58", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz", + "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==", "dev": true, "license": "MIT", "engines": { @@ -2899,9 +2947,9 @@ } }, "node_modules/object-deep-merge": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.0.tgz", - "integrity": "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.1.tgz", + "integrity": "sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==", "dev": true, "license": "MIT" }, @@ -2970,9 +3018,9 @@ } }, "node_modules/package-json-validator": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/package-json-validator/-/package-json-validator-1.5.1.tgz", - "integrity": "sha512-5WpI9I6pMy/ueq7rkDcJXfVjcHX6OBVTQk4Z7ObVZzsgPv/sX3kUfFFfvobErl/HpgleLcW4egvNypyBwGlkpw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/package-json-validator/-/package-json-validator-1.6.1.tgz", + "integrity": "sha512-eyl3DGFoxdUO0RA1Ym+H11S6P2XWIlA6Sy2PbvslbwFJ14kkz+FgX1Br3MaebrvLY3Q10Iih3PsL3741vsevrA==", "dev": true, "license": "MIT", "dependencies": { @@ -3132,20 +3180,10 @@ "dev": true, "license": "MIT" }, - "node_modules/regexp-tree": { - "version": "0.1.27", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", - "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", - "dev": true, - "license": "MIT", - "bin": { - "regexp-tree": "bin/regexp-tree" - } - }, "node_modules/regjsparser": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", - "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.4.tgz", + "integrity": "sha512-EWV80YgFaVgFrjGCAsV7oybp4GOxbls5RzBg8OB0nKEVXsBJR1JlV8Di1JhCROg3MzTfe1RI8yitIm1eOs68iA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3214,9 +3252,9 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3257,9 +3295,9 @@ "license": "MIT" }, "node_modules/sort-package-json": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-3.6.1.tgz", - "integrity": "sha512-Chgejw1+10p2D0U2tB7au1lHtz6TkFnxmvZktyBCRyV0GgmF6nl1IxXxAsPtJVsUyg/fo+BfCMAVVFUVRkAHrQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-4.0.0.tgz", + "integrity": "sha512-6aYOlYI9AWioZ+rzu+4zKLmoFqJP0/fHDxrd7X04yqEibikY+5YVF0EYlyGn4v6X2PJY7yAUWV7oeP+i5rOm/g==", "dev": true, "license": "MIT", "dependencies": { @@ -3275,7 +3313,7 @@ "sort-package-json": "cli.js" }, "engines": { - "node": ">=20" + "node": ">=22" } }, "node_modules/source-map-js": { @@ -3319,9 +3357,9 @@ "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-5.0.0.tgz", + "integrity": "sha512-vngmw3Rgn+o2arXNbnZaj5UtOEBuWBfvaI+Wc8GFfykIhA5/vdK9/Sp/XkLv63dykz2rxKDvKEHupF5P0FORcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3330,9 +3368,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.24.tgz", + "integrity": "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==", "dev": true, "license": "CC0-1.0" }, @@ -3357,9 +3395,9 @@ } }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "dev": true, "license": "MIT", "engines": { @@ -3437,29 +3475,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-declaration-location": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", - "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", - "dev": true, - "funding": [ - { - "type": "ko-fi", - "url": "https://ko-fi.com/rebeccastevens" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/ts-declaration-location" - } - ], - "license": "BSD-3-Clause", - "dependencies": { - "picomatch": "^4.0.2" - }, - "peerDependencies": { - "typescript": ">=4.0.0" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3519,9 +3534,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", - "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", "dev": true, "funding": [ { @@ -3591,6 +3606,19 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/verkit": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.3.2.tgz", + "integrity": "sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/vite": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.2.tgz", diff --git a/package.json b/package.json index 0a0f52d3..af6235a6 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@biomejs/biome": "^2.5.15", "@eslint/compat": "^2.1.1", - "@feedic/eslint-config": "^0.4.1", + "@feedic/eslint-config": "^0.5.0", "@types/node": "^26.6.4", "eslint": "^10.12.0", "eslint-config-biome": "^2.1.3", diff --git a/src/compile.ts b/src/compile.ts index e7ab3c10..a804aa63 100644 --- a/src/compile.ts +++ b/src/compile.ts @@ -4,7 +4,7 @@ import { compileGeneralSelector } from "./general.js"; import { getElementParent } from "./helpers/querying.js"; import { getQuality, - includesScopePseudo, + hasScopePseudo, isTraversal, sortRules, } from "./helpers/selectors.js"; @@ -28,7 +28,7 @@ const SCOPE_TOKEN: Selector = { /* * CSS 4 Spec (Draft): 3.4.1. Absolutizing a Relative Selector - * http://www.w3.org/TR/selectors4/#absolutizing + * https://www.w3.org/TR/selectors4/#absolutizing */ function absolutize( token: InternalSelector[][], @@ -50,7 +50,7 @@ function absolutize( t[0].type !== SelectorType.Descendant ) { // Don't continue in else branch - } else if (hasContext && !t.some(includesScopePseudo)) { + } else if (hasContext && !t.some(hasScopePseudo)) { t.unshift(DESCENDANT_TOKEN); } else { continue; diff --git a/src/general.ts b/src/general.ts index 87574a28..0ba02518 100644 --- a/src/general.ts +++ b/src/general.ts @@ -76,7 +76,7 @@ export function compileGeneralSelector( name = name.toLowerCase(); } - return function tag(element: ElementNode): boolean { + return function isTag(element: ElementNode): boolean { return adapter.getName(element) === name && next(element); }; } @@ -88,7 +88,7 @@ export function compileGeneralSelector( cacheResults === false || typeof WeakMap === "undefined" ) { - return function descendant(element: ElementNode): boolean { + return function isDescendant(element: ElementNode): boolean { let current: ElementNode | null = element; while ((current = getElementParent(current, adapter))) { @@ -106,7 +106,7 @@ export function compileGeneralSelector( ElementNode, { matches: boolean } >(); - return function cachedDescendant(element: ElementNode): boolean { + return function isCachedDescendant(element: ElementNode): boolean { let current: ElementNode | null = element; let result: { matches: boolean } | undefined; @@ -133,7 +133,9 @@ export function compileGeneralSelector( } case "_flexibleDescendant": { // Include element itself, only used while querying an array - return function flexibleDescendant(element: ElementNode): boolean { + return function isFlexibleDescendant( + element: ElementNode, + ): boolean { let current: ElementNode | null = element; do { @@ -147,20 +149,20 @@ export function compileGeneralSelector( }; } case SelectorType.Parent: { - return function parent(element: ElementNode): boolean { + return function isParent(element: ElementNode): boolean { return adapter .getChildren(element) .some((element) => adapter.isTag(element) && next(element)); }; } case SelectorType.Child: { - return function child(element: ElementNode): boolean { + return function isChild(element: ElementNode): boolean { const parent = getElementParent(element, adapter); return parent !== null && next(parent); }; } case SelectorType.Sibling: { - return function sibling(element: ElementNode): boolean { + return function isSibling(element: ElementNode): boolean { const siblings = adapter.getSiblings(element); for (const currentSibling of siblings) { @@ -177,14 +179,14 @@ export function compileGeneralSelector( } case SelectorType.Adjacent: { if (adapter.prevElementSibling) { - return function adjacent(element: ElementNode): boolean { + return function isAdjacent(element: ElementNode): boolean { // biome-ignore lint/style/noNonNullAssertion: checked by if statement const previous = adapter.prevElementSibling!(element); return previous != null && next(previous); }; } - return function adjacent(element: ElementNode): boolean { + return function isAdjacent(element: ElementNode): boolean { const siblings = adapter.getSiblings(element); let lastElement: ElementNode | undefined; diff --git a/src/helpers/cache.ts b/src/helpers/cache.ts index e92241e4..53bb3c2e 100644 --- a/src/helpers/cache.ts +++ b/src/helpers/cache.ts @@ -13,15 +13,15 @@ import { getElementParent } from "./querying.js"; * @param options Configuration object for cache behavior. * @param options.adapter Adapter implementation used for DOM access. * @param options.cacheResults Whether results should be memoized by input root. - * @param matches Compiled matcher function to wrap with caching. + * @param isMatch Compiled matcher function to wrap with caching. */ export function cacheParentResults( next: CompiledQuery, { adapter, cacheResults }: InternalOptions, - matches: (element: ElementNode) => boolean, + isMatch: (element: ElementNode) => boolean, ): CompiledQuery { if (cacheResults === false || typeof WeakMap === "undefined") { - return (element) => next(element) && matches(element); + return (element) => next(element) && isMatch(element); } // Use a cache to avoid re-checking children of an element. @@ -30,10 +30,10 @@ export function cacheParentResults( const resultCache = new WeakMap(); function addResultToCache(element: ElementNode) { - const result = matches(element); + const isMatched = isMatch(element); - resultCache.set(element, result); - return result; + resultCache.set(element, isMatched); + return isMatched; } return function cachedMatcher(element) { diff --git a/src/helpers/options.ts b/src/helpers/options.ts index 0efd95c1..1471b8a8 100644 --- a/src/helpers/options.ts +++ b/src/helpers/options.ts @@ -5,6 +5,7 @@ import type { InternalOptions } from "../types.js"; * * This is used when compiling nested selectors (e.g. inside `:is`, `:not`, * `:nth-child(… of S)`) so that the parent compilation state doesn't leak. + * @param options The parent compilation options. */ export function copyOptions( options: InternalOptions, diff --git a/src/helpers/selectors.ts b/src/helpers/selectors.ts index 3569d0df..298d6b61 100644 --- a/src/helpers/selectors.ts +++ b/src/helpers/selectors.ts @@ -1,3 +1,4 @@ +/* eslint-disable unicorn/prefer-includes-over-repeated-comparisons -- Keep selector quality comparisons allocation free. */ import { AttributeAction, type AttributeSelector, @@ -126,11 +127,11 @@ export function getQuality(token: InternalSelector): number { * Check whether a token or nested token includes `:scope`. * @param t Selector token under inspection. */ -export function includesScopePseudo(t: InternalSelector): boolean { +export function hasScopePseudo(t: InternalSelector): boolean { return ( t.type === SelectorType.Pseudo && (t.name === "scope" || (Array.isArray(t.data) && - t.data.some((data) => data.some(includesScopePseudo)))) + t.data.some((data) => data.some(hasScopePseudo)))) ); } diff --git a/src/index.ts b/src/index.ts index 1c90114b..94ba1fb6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,10 +17,10 @@ import type { Query, } from "./types.js"; -const defaultEquals = (a: Node, b: Node) => a === b; +const isEqualDefault = (a: Node, b: Node) => a === b; const defaultOptions: InternalOptions = { adapter: { ...DomUtils, isTag }, - equals: defaultEquals, + equals: isEqualDefault, }; function convertOptionFormats( @@ -34,7 +34,7 @@ function convertOptionFormats( // @ts-expect-error Same as above. finalOptions.adapter ??= defaultOptions.adapter; // @ts-expect-error `equals` does not exist on `Options` - finalOptions.equals ??= finalOptions.adapter?.equals ?? defaultEquals; + finalOptions.equals ??= finalOptions.adapter?.equals ?? isEqualDefault; return finalOptions as InternalOptions; } @@ -207,6 +207,7 @@ export const selectOne: ( * @see compile for supported selector queries. * @returns Whether the element matches the query. */ +// eslint-disable-next-line unicorn/consistent-boolean-name -- Preserve the existing exported function name. export function is( element: ElementNode, query: Query, diff --git a/src/pseudo-selectors/filters.ts b/src/pseudo-selectors/filters.ts index ea98cd98..1ebaabae 100644 --- a/src/pseudo-selectors/filters.ts +++ b/src/pseudo-selectors/filters.ts @@ -11,7 +11,7 @@ import type { CompiledQuery, CompileToken, InternalOptions } from "../types.js"; * @param tag - Lowercased subtags of the element's language value. * @param range - Lowercased subtags of the language range to match against. */ -function extendedFilter(tag: string[], range: string[]): boolean { +function isExtendedFilter(tag: string[], range: string[]): boolean { if (range[0] !== "*" && range[0] !== tag[0]) return false; let tagIndex = 1; @@ -43,10 +43,13 @@ export type Filter = ( compileToken?: CompileToken, ) => CompiledQuery; -function compileNth(reverse: boolean, ofType: boolean): Filter { +function compileNth( + shouldCountFromEnd: boolean, + shouldCountSameType: boolean, +): Filter { return function nth(next, rule, options, context, compileToken) { const { adapter, equals } = options; - const ofMatch = ofType ? null : rule.match(nthOfRegex); + const ofMatch = shouldCountSameType ? null : rule.match(nthOfRegex); const nthCheck = getNCheck(ofMatch ? ofMatch[1].trim() : rule); if (nthCheck === boolbase.falseFunc) return boolbase.falseFunc; @@ -72,12 +75,12 @@ function compileNth(reverse: boolean, ofType: boolean): Filter { const shouldCount = ofSelector ? (_element: ElementNode, sibling: ElementNode) => ofSelector(sibling) - : ofType + : shouldCountSameType ? (element: ElementNode, sibling: ElementNode) => adapter.getName(sibling) === adapter.getName(element) : boolbase.trueFunc; - if (reverse) { + if (shouldCountFromEnd) { return function nthLast(element) { if (ofSelector && !ofSelector(element)) return false; const siblings = adapter.getSiblings(element); @@ -186,7 +189,7 @@ export const filters: Record = { const tag = value.toLowerCase().split("-"); return ( - ranges.some((r) => extendedFilter(tag, r)) && + ranges.some((r) => isExtendedFilter(tag, r)) && next(element) ); } @@ -222,7 +225,7 @@ function dynamicStatePseudo( return boolbase.falseFunc; } - return function active(element) { + return function isActive(element) { return filterFunction(element) && next(element); }; }; diff --git a/src/pseudo-selectors/index.ts b/src/pseudo-selectors/index.ts index d711a337..b4cf903d 100644 --- a/src/pseudo-selectors/index.ts +++ b/src/pseudo-selectors/index.ts @@ -38,14 +38,17 @@ export function compilePseudoSelector( const { name, data } = selector; if (Array.isArray(data)) { - if (!(name in subselects)) { + if (!Object.hasOwn(subselects, name)) { throw new Error(`Unknown pseudo-class :${name}(${data})`); } return subselects[name](next, data, options, context, compileToken); } - const userPseudo = options.pseudos?.[name]; + const userPseudo = + options.pseudos && Object.hasOwn(options.pseudos, name) + ? options.pseudos[name] + : undefined; const stringPseudo = typeof userPseudo === "string" ? userPseudo : aliases[name]; @@ -66,7 +69,7 @@ export function compilePseudoSelector( return (element) => userPseudo(element, data) && next(element); } - if (name in filters) { + if (Object.hasOwn(filters, name)) { return filters[name]( next, data as string, @@ -76,7 +79,7 @@ export function compilePseudoSelector( ); } - if (name in pseudos) { + if (Object.hasOwn(pseudos, name)) { const pseudo = pseudos[name]; verifyPseudoArguments(pseudo, name, data, 2); diff --git a/src/pseudo-selectors/pseudos.ts b/src/pseudo-selectors/pseudos.ts index 99b5c24c..81c89cd7 100644 --- a/src/pseudo-selectors/pseudos.ts +++ b/src/pseudo-selectors/pseudos.ts @@ -13,7 +13,7 @@ type Pseudo = ( * line feeds. * @see {@link https://www.w3.org/TR/css-text-3/#white-space} */ -const isDocumentWhiteSpace = /^[ \t\r\n]*$/; +const documentWhitespaceRegex = /^[ \t\r\n]*$/; // While filters are precompiled, pseudos get called when they are needed /** Runtime pseudo selector implementations. */ @@ -26,7 +26,7 @@ export const pseudos: Record = { // Then, check that the text content is only whitespace. children.every((element) => // FIXME: `getText` call is potentially expensive. - isDocumentWhiteSpace.test(adapter.getText(element)), + documentWhitespaceRegex.test(adapter.getText(element)), ) ); }, @@ -116,22 +116,22 @@ export const pseudos: Record = { /** * Validate pseudo selector argument arity. - * @param pseudoClassCondition Pseudo-function implementation to wrap. + * @param isPseudoClassCondition Pseudo-function implementation to wrap. * @param name Name of the pseudo selector. * @param subselect Subselector passed to the pseudo-function. * @param argumentIndex Index of the argument parser to apply. */ export function verifyPseudoArguments( - pseudoClassCondition: (...parameters: T) => boolean, + isPseudoClassCondition: (...parameters: T) => boolean, name: string, subselect: PseudoSelector["data"], argumentIndex: number, ): void { if (subselect === null) { - if (pseudoClassCondition.length > argumentIndex) { + if (isPseudoClassCondition.length > argumentIndex) { throw new Error(`Pseudo-class :${name} requires an argument`); } - } else if (pseudoClassCondition.length === argumentIndex) { + } else if (isPseudoClassCondition.length === argumentIndex) { throw new Error(`Pseudo-class :${name} doesn't have any arguments`); } } diff --git a/src/pseudo-selectors/subselects.ts b/src/pseudo-selectors/subselects.ts index 19f2762a..e93f4804 100644 --- a/src/pseudo-selectors/subselects.ts +++ b/src/pseudo-selectors/subselects.ts @@ -3,7 +3,7 @@ import type { Selector } from "css-what"; import { cacheParentResults } from "../helpers/cache.js"; import { copyOptions } from "../helpers/options.js"; import { findOne, getNextSiblings } from "../helpers/querying.js"; -import { includesScopePseudo, isTraversal } from "../helpers/selectors.js"; +import { hasScopePseudo, isTraversal } from "../helpers/selectors.js"; import type { CompiledQuery, CompileToken, InternalOptions } from "../types.js"; /** Used as a placeholder for :has. Will be replaced with the actual element. */ @@ -26,11 +26,10 @@ type Subselect = ( * @param selector - The selector to check. * @returns Whether the selector has any properties that rely on the current element. */ -function hasDependsOnCurrentElement(selector: Selector[][]) { +function isContextDependent(selector: Selector[][]) { return selector.some( (sel) => - sel.length > 0 && - (isTraversal(sel[0]) || sel.some(includesScopePseudo)), + sel.length > 0 && (isTraversal(sel[0]) || sel.some(hasScopePseudo)), ); } @@ -86,7 +85,7 @@ export const subselects: Record = { ? // Used as a placeholder. Will be replaced with the actual element. [PLACEHOLDER_ELEMENT as unknown as ElementNode] : undefined; - const skipCache = hasDependsOnCurrentElement(subselect); + const shouldSkipCache = isContextDependent(subselect); const compiled = compileToken(subselect, copiedOptions, context); @@ -96,7 +95,7 @@ export const subselects: Record = { // If `compiled` is `trueFunc`, we can skip this. if (context && compiled !== boolbase.trueFunc) { - return skipCache + return shouldSkipCache ? (element) => { if (!next(element)) { return false; @@ -134,7 +133,7 @@ export const subselects: Record = { const hasOne = (element: ElementNode) => findOne(compiled, adapter.getChildren(element), options) !== null; - return skipCache + return shouldSkipCache ? (element) => next(element) && hasOne(element) : cacheParentResults(next, options, hasOne); }, diff --git a/test/api.ts b/test/api.ts index 97884377..74c8c997 100644 --- a/test/api.ts +++ b/test/api.ts @@ -133,6 +133,26 @@ describe("API", () => { }); describe("errors", () => { + it.each([":constructor", ":toString", ":constructor(div)"])( + "should reject inherited pseudo name %s", + (selector) => { + expect(() => CSSselect.compile(selector)).toThrow( + "Unknown pseudo-class", + ); + expect(() => + CSSselect.compile(selector, { pseudos: {} }), + ).toThrow("Unknown pseudo-class"); + }, + ); + + it("should allow an own custom pseudo named constructor", () => { + expect( + CSSselect.is(dom, ":constructor", { + pseudos: { constructor: () => true }, + }), + ).toBe(true); + }); + it("should throw with a pseudo-element", () => { expect(() => CSSselect.compile("::after")).toThrow("not supported"); }); @@ -441,7 +461,7 @@ describe("API", () => { }); it("should support isHovered", () => { - const dom = parseDocument(`${"

foo".repeat(10)}`) + const dom = parseDocument("

foo".repeat(10)) .children as Element[]; const adapter = { @@ -457,7 +477,7 @@ describe("API", () => { }); it("should not match any elements if `isHovered` is not defined", () => { - const dom = parseDocument(`${"

foo".repeat(10)}`); + const dom = parseDocument("

foo".repeat(10)); expect(CSSselect.selectAll("p:hover", dom)).toHaveLength(0); }); }); diff --git a/test/nwmatcher.ts b/test/nwmatcher.ts index 69f9cf90..74e13153 100644 --- a/test/nwmatcher.ts +++ b/test/nwmatcher.ts @@ -1,7 +1,7 @@ /** * The NWMatcher Test Suite. * Adapted from https://github.com/dperini/nwmatcher/blob/master/test/scotch/test.js - * (c) 2007-2013 Diego Perini (http://www.iport.it) + * (c) 2007-2013 Diego Perini (https://www.iport.it) * * See the LICENSE file for additional information. */ @@ -568,7 +568,7 @@ describe("NWMatcher", () => { ); /* * Opera 10 does not accept values > 128 as a parameter to :nth-child - * See + * See */ expect(select("ul > li:nth-child(n-128)")).toStrictEqual( select("ul > li"), diff --git a/test/sizzle.ts b/test/sizzle.ts index c190ee01..a68d44cd 100755 --- a/test/sizzle.ts +++ b/test/sizzle.ts @@ -15,6 +15,7 @@ let document = loadDocument(); describe("Sizzle", () => { beforeEach(() => { + // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Reset the shared fixture before each test. document = loadDocument(); }); @@ -38,9 +39,11 @@ describe("Sizzle", () => { // Select all expect(CSSselect.selectAll("*", document).length >= 30).toBe(true); const all = CSSselect.selectAll("*", document); - const good = all.every((element: AnyNode) => element.nodeType !== 8); + const hasNoComments = all.every( + (element: AnyNode) => element.nodeType !== 8, + ); // Select all elements, no comment nodes - expect(good).toBe(true); + expect(hasNoComments).toBe(true); // Element Selector t("html", ["html"]); // Element Selector @@ -362,7 +365,8 @@ describe("Sizzle", () => { )?.attribs["id"], ); - for (const node of parseDOM(String.raw``)) { + const escapedLink = parseDOM(String.raw``); + for (const node of escapedLink) { DomUtils.appendChild(document.getElementById("form"), node); } @@ -736,6 +740,7 @@ describe("Sizzle", () => { // Attribute Equals t("#qunit-fixture a[rel=bookmark]", ["simon1"]); // Attribute Equals + // eslint-disable-next-line unicorn/prefer-https -- Match the original HTTP fixture exactly. t("#qunit-fixture a[href='http://www.google.com/']", ["google"]); // Attribute Equals t("#qunit-fixture a[ rel = 'bookmark' ]", ["simon1"]); @@ -939,7 +944,7 @@ describe("Sizzle", () => { t(String.raw`input[data-attr='\04e00']`, ["attrbad_unicode"]); document.getElementById("attrbad_unicode").attribs["data-attr"] = - "\uD834\uDF06A"; + "\u{1D306}A"; /* * It was too much code to fix Safari 5.x Supplemental Plane crashes (see ba5f09fa404379a87370ec905ffa47f8ac40aaa3) * Long numeric escape (non-BMP) @@ -1417,7 +1422,7 @@ describe("Sizzle", () => { ...parseDOM( "".replace( /%/g, - type, + () => type, ), ), ]; // Create a copy of the array, so that `appendChild` doesn't remove the elements. diff --git a/test/tools/helper.ts b/test/tools/helper.ts index 8188f35e..f5640d0a 100644 --- a/test/tools/helper.ts +++ b/test/tools/helper.ts @@ -39,7 +39,8 @@ export function getDocument(file: string): SimpleDocument { document.createTextNode = (content: string) => new Text(content); document.createElement = (name: string) => new Element(name.toLowerCase(), {}); - [document.body] = DomUtils.getElementsByTagName("body", document, true, 1); + const [body] = DomUtils.getElementsByTagName("body", document, true, 1); + document.body = body; const documentElement = document.children.find(isTag); assert.ok(documentElement, "Did not find document element"); diff --git a/test/tools/sizzle-testinit.ts b/test/tools/sizzle-testinit.ts index 20bf3fd1..1e19ee3e 100755 --- a/test/tools/sizzle-testinit.ts +++ b/test/tools/sizzle-testinit.ts @@ -6,6 +6,7 @@ import * as helper from "./helper.js"; let document = helper.getDocument("sizzle.html"); export function loadDocument(): helper.SimpleDocument { + // eslint-disable-next-line unicorn/no-top-level-assignment-in-function -- Reset the shared fixture before each test. document = helper.getDocument("sizzle.html"); return document; }