From 3c33a0e4eb482975b13c9622f044b1f2bfb8ddd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 13:35:07 +0000 Subject: [PATCH 01/13] build(deps-dev): bump @feedic/eslint-config from 0.4.1 to 0.5.0 Bumps [@feedic/eslint-config](https://github.com/fb55/eslint-config) from 0.4.1 to 0.5.0. - [Commits](https://github.com/fb55/eslint-config/compare/v0.4.1...v0.5.0) --- updated-dependencies: - dependency-name: "@feedic/eslint-config" dependency-version: 0.5.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 390 +++++++++++++++++++++++----------------------- package.json | 2 +- 2 files changed, 196 insertions(+), 196 deletions(-) diff --git a/package-lock.json b/package-lock.json index 384cec46..5440cd55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "devDependencies": { "@biomejs/biome": "^2.5.2", "@eslint/compat": "^2.1.0", - "@feedic/eslint-config": "^0.4.1", + "@feedic/eslint-config": "^0.5.0", "@types/node": "^26.3.0", "eslint": "^10.9.1", "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.0", + "resolved": "https://registry.npmjs.org/@altano/repository-tools/-/repository-tools-2.1.0.tgz", + "integrity": "sha512-RGM8IimoK/RDlj+uG9sPgb9CSuHIAMVrtVVeslEMhc7yqDHpdmdKeMQcJfc0WveTtAmBdj/o9r1VW/A27SoPTg==", "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": { @@ -229,17 +229,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" @@ -405,18 +405,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" @@ -820,9 +820,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" }, @@ -1257,9 +1257,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.27", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", - "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1296,9 +1296,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "dev": true, "funding": [ { @@ -1316,11 +1316,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" + "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" }, "bin": { "browserslist": "cli.js" @@ -1330,9 +1330,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.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.3.0.tgz", + "integrity": "sha512-hMQUl2bUFG339QygPM97E+mc8OY1IAchORZxm4a/frcYwKzozMzRVDBwHW0NjOqGElLm2O37AVQE8ikxlZHrMQ==", "dev": true, "license": "MIT", "engines": { @@ -1343,9 +1343,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001791", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", - "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", "dev": true, "funding": [ { @@ -1396,33 +1396,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": { @@ -1437,13 +1414,16 @@ "license": "MIT" }, "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", @@ -1607,21 +1587,21 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.349", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz", - "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==", + "version": "1.5.416", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.416.tgz", + "integrity": "sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA==", "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.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.3.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -1752,9 +1732,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.3", + "resolved": "https://registry.npmjs.org/eslint-fix-utils/-/eslint-fix-utils-0.4.3.tgz", + "integrity": "sha512-EKzNhsNavV5DdkHVltHBM9TqfsonCmiUhOm1Ra97zo03M9IAx3wtM1eC27eWGMj/D3LrALmHHNe1QlQH1P7Nxw==", "dev": true, "license": "MIT", "engines": { @@ -1770,6 +1750,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", @@ -1793,38 +1795,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.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.3.0.tgz", + "integrity": "sha512-cPVguuDe6DrIPb/qUXHf8P89MaVTUmiYWwpt5gX5AILsvRIiZAxMFXcFR6QHYBksqKJpjfUBlL/RleCJUWcD7w==", "dev": true, "license": "MIT", "dependencies": { @@ -1835,17 +1837,26 @@ "globals": "^15.11.0", "globrex": "^0.1.2", "ignore": "^5.3.2", - "semver": "^7.6.3", - "ts-declaration-location": "^1.0.6" + "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": { @@ -1862,63 +1873,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.8.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-package-json/-/eslint-plugin-package-json-1.8.0.tgz", + "integrity": "sha512-nfWWrak5t8yPgSVchNOHCjdFW/pl3+1135Bxsq0KZKo6PUdWWqUTqOVBrvzw1S+8BNOhhEx5c/YxeeCSYySFog==", "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": { @@ -2156,9 +2174,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": { @@ -2364,9 +2382,9 @@ "license": "ISC" }, "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": { @@ -2408,16 +2426,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" @@ -2740,9 +2757,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.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -2809,11 +2826,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.38", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", - "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/npm-package-arg": { "version": "13.0.2", @@ -2848,9 +2868,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" }, @@ -2916,9 +2936,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": { @@ -3064,20 +3084,10 @@ "node": ">=6" } }, - "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.2", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", + "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3145,9 +3155,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": { @@ -3195,9 +3205,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": { @@ -3213,7 +3223,7 @@ "sort-package-json": "cli.js" }, "engines": { - "node": ">=20" + "node": ">=22" } }, "node_modules/source-map-js": { @@ -3256,9 +3266,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": { @@ -3301,9 +3311,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": { @@ -3388,29 +3398,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", @@ -3470,9 +3457,9 @@ "license": "MIT" }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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==", "dev": true, "funding": [ { @@ -3542,6 +3529,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.2.2", "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", diff --git a/package.json b/package.json index 2a5d4667..5ceeffdf 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@biomejs/biome": "^2.5.2", "@eslint/compat": "^2.1.0", - "@feedic/eslint-config": "^0.4.1", + "@feedic/eslint-config": "^0.5.0", "@types/node": "^26.3.0", "eslint": "^10.9.1", "eslint-config-biome": "^2.1.3", From 0c6f3c67f41b3aa8f9305a90c5ad7958e4f0294b Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Fri, 9 Oct 2026 14:15:15 +0100 Subject: [PATCH 02/13] chore: adapt shared lint rules and require manual review --- .github/workflows/dependabot-automerge.yml | 10 ++-- eslint.config.mjs | 54 ++++++++++++++++++++++ src/compile.ts | 2 +- src/helpers/options.ts | 1 + src/helpers/selectors.ts | 4 +- src/pseudo-selectors/pseudos.ts | 4 +- test/api.ts | 4 +- test/sizzle.ts | 11 +++-- test/tools/helper.ts | 3 +- 9 files changed, 74 insertions(+), 19 deletions(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 41481430..3213a9db 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -18,11 +18,11 @@ jobs: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs # Automatically merge semver-patch and semver-minor PRs - if: - "${{ steps.metadata.outputs.update-type == - 'version-update:semver-minor' || - steps.metadata.outputs.update-type == - 'version-update:semver-patch' }}" + # Shared lint rule changes require manual review. + if: >- + ${{ !contains(steps.metadata.outputs.dependency-names, '@feedic/eslint-config') && + (steps.metadata.outputs.update-type == 'version-update:semver-minor' || + steps.metadata.outputs.update-type == 'version-update:semver-patch') }} run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} diff --git a/eslint.config.mjs b/eslint.config.mjs index e4aa5d7b..aaa798a8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -47,4 +47,58 @@ export default defineConfig([ }, }, eslintConfigBiome, +// These nodes implement domhandler APIs, not the browser DOM. +{ + "files": [ + "**/*.ts" + ], + "rules": { + "unicorn/better-dom-traversing": "off" + } +}, +// Preserve established public predicate names and selector vocabulary. +{ + "files": [ + "src/general.ts", + "src/helpers/cache.ts", + "src/helpers/selectors.ts", + "src/index.ts", + "src/pseudo-selectors/filters.ts", + "src/pseudo-selectors/pseudos.ts", + "src/pseudo-selectors/subselects.ts" + ], + "rules": { + "unicorn/consistent-boolean-name": "off" + } +}, +// HTTP URLs are intentional fixture inputs and assertions. +{ + "files": [ + "**/*.spec.ts", + "test/**/*.ts" + ], + "rules": { + "unicorn/prefer-https": "off" + } +}, +// Pseudo-selector names dynamically index the selector dispatch tables. +{ + "files": [ + "src/pseudo-selectors/index.ts" + ], + "rules": { + "unicorn/no-computed-property-existence-check": "off" + } +}, +// The upstream Sizzle suite initializes its shared document fixture per test. +{ + "files": [ + "test/sizzle.ts", + "test/tools/sizzle-testinit.ts" + ], + "rules": { + "unicorn/no-top-level-assignment-in-function": "off" + } +}, + ]); diff --git a/src/compile.ts b/src/compile.ts index e7ab3c10..83fa8606 100644 --- a/src/compile.ts +++ b/src/compile.ts @@ -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[][], 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..385b8fc1 100644 --- a/src/helpers/selectors.ts +++ b/src/helpers/selectors.ts @@ -97,9 +97,7 @@ export function getQuality(token: InternalSelector): number { } case SelectorType.Pseudo: { return token.data - ? token.name === "has" || - token.name === "contains" || - token.name === "icontains" + ? ["has", "contains", "icontains"].includes(token.name) ? // Expensive in any case — run as late as possible. 0 : Array.isArray(token.data) diff --git a/src/pseudo-selectors/pseudos.ts b/src/pseudo-selectors/pseudos.ts index 99b5c24c..ddb39ca2 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 isDocumentWhitespace = /^[ \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)), + isDocumentWhitespace.test(adapter.getText(element)), ) ); }, diff --git a/test/api.ts b/test/api.ts index 97884377..07e65f12 100644 --- a/test/api.ts +++ b/test/api.ts @@ -441,7 +441,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 +457,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/sizzle.ts b/test/sizzle.ts index bc6e92e9..9eb25237 100755 --- a/test/sizzle.ts +++ b/test/sizzle.ts @@ -38,9 +38,9 @@ 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 isGood = all.every((element: AnyNode) => element.nodeType !== 8); // Select all elements, no comment nodes - expect(good).toBe(true); + expect(isGood).toBe(true); // Element Selector t("html", ["html"]); // Element Selector @@ -359,7 +359,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); } @@ -936,7 +937,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) @@ -1414,7 +1415,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"); From 92357c41d6250c481f8154b9b81bc52918ee4c79 Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:33:04 +0100 Subject: [PATCH 03/13] style: prefer surgical fixes for updated lint rules --- eslint.config.mjs | 61 ++++-------------------------- src/general.ts | 20 +++++----- src/helpers/cache.ts | 12 +++--- src/helpers/selectors.ts | 1 + src/index.ts | 7 ++-- src/pseudo-selectors/filters.ts | 14 +++---- src/pseudo-selectors/index.ts | 6 +-- src/pseudo-selectors/pseudos.ts | 8 ++-- src/pseudo-selectors/subselects.ts | 6 +-- test/nwmatcher.ts | 4 +- test/sizzle.ts | 2 + test/tools/sizzle-testinit.ts | 1 + 12 files changed, 51 insertions(+), 91 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index aaa798a8..315617c2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -47,58 +47,11 @@ export default defineConfig([ }, }, eslintConfigBiome, -// These nodes implement domhandler APIs, not the browser DOM. -{ - "files": [ - "**/*.ts" - ], - "rules": { - "unicorn/better-dom-traversing": "off" - } -}, -// Preserve established public predicate names and selector vocabulary. -{ - "files": [ - "src/general.ts", - "src/helpers/cache.ts", - "src/helpers/selectors.ts", - "src/index.ts", - "src/pseudo-selectors/filters.ts", - "src/pseudo-selectors/pseudos.ts", - "src/pseudo-selectors/subselects.ts" - ], - "rules": { - "unicorn/consistent-boolean-name": "off" - } -}, -// HTTP URLs are intentional fixture inputs and assertions. -{ - "files": [ - "**/*.spec.ts", - "test/**/*.ts" - ], - "rules": { - "unicorn/prefer-https": "off" - } -}, -// Pseudo-selector names dynamically index the selector dispatch tables. -{ - "files": [ - "src/pseudo-selectors/index.ts" - ], - "rules": { - "unicorn/no-computed-property-existence-check": "off" - } -}, -// The upstream Sizzle suite initializes its shared document fixture per test. -{ - "files": [ - "test/sizzle.ts", - "test/tools/sizzle-testinit.ts" - ], - "rules": { - "unicorn/no-top-level-assignment-in-function": "off" - } -}, - + // These fixtures use domhandler nodes; browser querySelector and firstElementChild APIs do not apply. + { + files: ["test/api.ts", "test/pseudo-classes.ts", "test/sizzle.ts"], + rules: { + "unicorn/better-dom-traversing": "off", + }, + }, ]); 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..42bdcee7 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 isMatches Compiled matcher function to wrap with caching. */ export function cacheParentResults( next: CompiledQuery, { adapter, cacheResults }: InternalOptions, - matches: (element: ElementNode) => boolean, + isMatches: (element: ElementNode) => boolean, ): CompiledQuery { if (cacheResults === false || typeof WeakMap === "undefined") { - return (element) => next(element) && matches(element); + return (element) => next(element) && isMatches(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 isResult = isMatches(element); - resultCache.set(element, result); - return result; + resultCache.set(element, isResult); + return isResult; } return function cachedMatcher(element) { diff --git a/src/helpers/selectors.ts b/src/helpers/selectors.ts index 385b8fc1..a3925064 100644 --- a/src/helpers/selectors.ts +++ b/src/helpers/selectors.ts @@ -124,6 +124,7 @@ export function getQuality(token: InternalSelector): number { * Check whether a token or nested token includes `:scope`. * @param t Selector token under inspection. */ +// eslint-disable-next-line unicorn/consistent-boolean-name -- Preserve the existing exported function name. export function includesScopePseudo(t: InternalSelector): boolean { return ( t.type === SelectorType.Pseudo && diff --git a/src/index.ts b/src/index.ts index 1c90114b..9d8e3913 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 isDefaultEquals = (a: Node, b: Node) => a === b; const defaultOptions: InternalOptions = { adapter: { ...DomUtils, isTag }, - equals: defaultEquals, + equals: isDefaultEquals, }; 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 ?? isDefaultEquals; 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 83f0a69d..21e3a5bf 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,10 @@ export type Filter = ( compileToken?: CompileToken, ) => CompiledQuery; -function compileNth(reverse: boolean, ofType: boolean): Filter { +function compileNth(isReverse: boolean, isOfType: boolean): Filter { return function nth(next, rule, options, context, compileToken) { const { adapter, equals } = options; - const ofMatch = ofType ? null : rule.match(nthOfRegex); + const ofMatch = isOfType ? null : rule.match(nthOfRegex); const nthCheck = getNCheck(ofMatch ? ofMatch[1].trim() : rule); if (nthCheck === boolbase.falseFunc) return boolbase.falseFunc; @@ -72,12 +72,12 @@ function compileNth(reverse: boolean, ofType: boolean): Filter { const shouldCount = ofSelector ? (_element: ElementNode, sibling: ElementNode) => ofSelector(sibling) - : ofType + : isOfType ? (element: ElementNode, sibling: ElementNode) => adapter.getName(sibling) === adapter.getName(element) : boolbase.trueFunc; - if (reverse) { + if (isReverse) { return function nthLast(element) { if (ofSelector && !ofSelector(element)) return false; const siblings = adapter.getSiblings(element); @@ -186,7 +186,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 +222,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 a853144d..23286536 100644 --- a/src/pseudo-selectors/index.ts +++ b/src/pseudo-selectors/index.ts @@ -38,7 +38,7 @@ export function compilePseudoSelector( const { name, data } = selector; if (Array.isArray(data)) { - if (!(name in subselects)) { + if (!Reflect.has(subselects, name)) { throw new Error(`Unknown pseudo-class :${name}(${data})`); } @@ -66,7 +66,7 @@ export function compilePseudoSelector( return (element) => userPseudo(element, data) && next(element); } - if (name in filters) { + if (Reflect.has(filters, name)) { return filters[name]( next, data as string, @@ -76,7 +76,7 @@ export function compilePseudoSelector( ); } - if (name in pseudos) { + if (Reflect.has(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 ddb39ca2..a8e93189 100644 --- a/src/pseudo-selectors/pseudos.ts +++ b/src/pseudo-selectors/pseudos.ts @@ -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..d5b9456b 100644 --- a/src/pseudo-selectors/subselects.ts +++ b/src/pseudo-selectors/subselects.ts @@ -86,7 +86,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 isSkipCache = hasDependsOnCurrentElement(subselect); const compiled = compileToken(subselect, copiedOptions, context); @@ -96,7 +96,7 @@ export const subselects: Record = { // If `compiled` is `trueFunc`, we can skip this. if (context && compiled !== boolbase.trueFunc) { - return skipCache + return isSkipCache ? (element) => { if (!next(element)) { return false; @@ -134,7 +134,7 @@ export const subselects: Record = { const hasOne = (element: ElementNode) => findOne(compiled, adapter.getChildren(element), options) !== null; - return skipCache + return isSkipCache ? (element) => next(element) && hasOne(element) : cacheParentResults(next, options, hasOne); }, 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 9eb25237..61084e6f 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(); }); @@ -734,6 +735,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"]); 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; } From ea3366ad1154deab338754d45b5642fa49716c02 Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Sat, 10 Oct 2026 00:03:48 +0100 Subject: [PATCH 04/13] fix: reject inherited built-in pseudos and validate development Node --- .github/workflows/nodejs-test.yml | 7 ++++--- .node-version | 1 + src/pseudo-selectors/index.ts | 6 +++--- test/api.ts | 10 ++++++++++ 4 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 .node-version diff --git a/.github/workflows/nodejs-test.yml b/.github/workflows/nodejs-test.yml index bb5e4cc4..a2c9e333 100644 --- a/.github/workflows/nodejs-test.yml +++ b/.github/workflows/nodejs-test.yml @@ -26,7 +26,7 @@ jobs: - run: npm run lint test: - name: Node ${{ matrix.node }} + name: Node ${{ matrix.node == '22.22.2' && '22' || matrix.node }} runs-on: ubuntu-latest strategy: @@ -34,8 +34,9 @@ jobs: matrix: node: - 20 - - 22 + - 22.22.2 # Minimum Node.js version for the development toolchain. - 24 + - 26 - lts/* steps: @@ -47,4 +48,4 @@ jobs: cache: npm - run: npm ci - run: npm run build --if-present - - run: npm run test:vi + - run: npm run ${{ matrix.node == '22.22.2' && 'test' || 'test:vi' }} diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..db49bb14 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.22.2 diff --git a/src/pseudo-selectors/index.ts b/src/pseudo-selectors/index.ts index 23286536..41de173b 100644 --- a/src/pseudo-selectors/index.ts +++ b/src/pseudo-selectors/index.ts @@ -38,7 +38,7 @@ export function compilePseudoSelector( const { name, data } = selector; if (Array.isArray(data)) { - if (!Reflect.has(subselects, name)) { + if (!Object.hasOwn(subselects, name)) { throw new Error(`Unknown pseudo-class :${name}(${data})`); } @@ -66,7 +66,7 @@ export function compilePseudoSelector( return (element) => userPseudo(element, data) && next(element); } - if (Reflect.has(filters, name)) { + if (Object.hasOwn(filters, name)) { return filters[name]( next, data as string, @@ -76,7 +76,7 @@ export function compilePseudoSelector( ); } - if (Reflect.has(pseudos, name)) { + if (Object.hasOwn(pseudos, name)) { const pseudo = pseudos[name]; verifyPseudoArguments(pseudo, name, data, 2); diff --git a/test/api.ts b/test/api.ts index 07e65f12..a04a7d19 100644 --- a/test/api.ts +++ b/test/api.ts @@ -133,6 +133,16 @@ 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", + ); + }); + it("should throw with a pseudo-element", () => { expect(() => CSSselect.compile("::after")).toThrow("not supported"); }); From 9c4758248b13179ecf338bc0e895296be7536f04 Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Sat, 10 Oct 2026 00:10:51 +0100 Subject: [PATCH 05/13] fix: reject inherited custom pseudo names --- src/pseudo-selectors/index.ts | 5 ++++- test/api.ts | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/pseudo-selectors/index.ts b/src/pseudo-selectors/index.ts index 41de173b..bce0a4e0 100644 --- a/src/pseudo-selectors/index.ts +++ b/src/pseudo-selectors/index.ts @@ -45,7 +45,10 @@ export function compilePseudoSelector( 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]; diff --git a/test/api.ts b/test/api.ts index a04a7d19..b98ba88e 100644 --- a/test/api.ts +++ b/test/api.ts @@ -141,6 +141,17 @@ describe("API", () => { 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", () => { From ba28f72cd3b0c230e4e5fa59730e6a7cc06a0c04 Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Sat, 10 Oct 2026 08:49:56 +0100 Subject: [PATCH 06/13] chore: clarify internal predicates and simplify CI --- .github/workflows/nodejs-test.yml | 6 +++--- .node-version | 2 +- eslint.config.mjs | 3 +-- src/compile.ts | 4 ++-- src/helpers/selectors.ts | 5 ++--- src/index.ts | 6 +++--- src/pseudo-selectors/subselects.ts | 5 ++--- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/nodejs-test.yml b/.github/workflows/nodejs-test.yml index a2c9e333..79262bd9 100644 --- a/.github/workflows/nodejs-test.yml +++ b/.github/workflows/nodejs-test.yml @@ -26,7 +26,7 @@ jobs: - run: npm run lint test: - name: Node ${{ matrix.node == '22.22.2' && '22' || matrix.node }} + name: Node ${{ matrix.node }} runs-on: ubuntu-latest strategy: @@ -34,7 +34,7 @@ jobs: matrix: node: - 20 - - 22.22.2 # Minimum Node.js version for the development toolchain. + - 22 - 24 - 26 - lts/* @@ -48,4 +48,4 @@ jobs: cache: npm - run: npm ci - run: npm run build --if-present - - run: npm run ${{ matrix.node == '22.22.2' && 'test' || 'test:vi' }} + - run: npm run test:vi diff --git a/.node-version b/.node-version index db49bb14..2bd5a0a9 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.22.2 +22 diff --git a/eslint.config.mjs b/eslint.config.mjs index 315617c2..8d574007 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -47,9 +47,8 @@ export default defineConfig([ }, }, eslintConfigBiome, - // These fixtures use domhandler nodes; browser querySelector and firstElementChild APIs do not apply. + // This module uses parser DOM nodes, which do not implement browser traversal APIs. { - files: ["test/api.ts", "test/pseudo-classes.ts", "test/sizzle.ts"], rules: { "unicorn/better-dom-traversing": "off", }, diff --git a/src/compile.ts b/src/compile.ts index 83fa8606..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"; @@ -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/helpers/selectors.ts b/src/helpers/selectors.ts index a3925064..ee8d1f81 100644 --- a/src/helpers/selectors.ts +++ b/src/helpers/selectors.ts @@ -124,12 +124,11 @@ export function getQuality(token: InternalSelector): number { * Check whether a token or nested token includes `:scope`. * @param t Selector token under inspection. */ -// eslint-disable-next-line unicorn/consistent-boolean-name -- Preserve the existing exported function name. -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 9d8e3913..94ba1fb6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,10 +17,10 @@ import type { Query, } from "./types.js"; -const isDefaultEquals = (a: Node, b: Node) => a === b; +const isEqualDefault = (a: Node, b: Node) => a === b; const defaultOptions: InternalOptions = { adapter: { ...DomUtils, isTag }, - equals: isDefaultEquals, + 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 ?? isDefaultEquals; + finalOptions.equals ??= finalOptions.adapter?.equals ?? isEqualDefault; return finalOptions as InternalOptions; } diff --git a/src/pseudo-selectors/subselects.ts b/src/pseudo-selectors/subselects.ts index d5b9456b..5e077202 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. */ @@ -29,8 +29,7 @@ type Subselect = ( function hasDependsOnCurrentElement(selector: Selector[][]) { return selector.some( (sel) => - sel.length > 0 && - (isTraversal(sel[0]) || sel.some(includesScopePseudo)), + sel.length > 0 && (isTraversal(sel[0]) || sel.some(hasScopePseudo)), ); } From 044553d1ab3ee1618fb587968db89b02fb74fe2a Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Sat, 10 Oct 2026 08:58:37 +0100 Subject: [PATCH 07/13] chore: clarify internal matcher and cache flag names --- src/helpers/cache.ts | 8 ++++---- src/pseudo-selectors/subselects.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/helpers/cache.ts b/src/helpers/cache.ts index 42bdcee7..cda3a633 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 isMatches Compiled matcher function to wrap with caching. + * @param isMatch Compiled matcher function to wrap with caching. */ export function cacheParentResults( next: CompiledQuery, { adapter, cacheResults }: InternalOptions, - isMatches: (element: ElementNode) => boolean, + isMatch: (element: ElementNode) => boolean, ): CompiledQuery { if (cacheResults === false || typeof WeakMap === "undefined") { - return (element) => next(element) && isMatches(element); + return (element) => next(element) && isMatch(element); } // Use a cache to avoid re-checking children of an element. @@ -30,7 +30,7 @@ export function cacheParentResults( const resultCache = new WeakMap(); function addResultToCache(element: ElementNode) { - const isResult = isMatches(element); + const isResult = isMatch(element); resultCache.set(element, isResult); return isResult; diff --git a/src/pseudo-selectors/subselects.ts b/src/pseudo-selectors/subselects.ts index 5e077202..e93f4804 100644 --- a/src/pseudo-selectors/subselects.ts +++ b/src/pseudo-selectors/subselects.ts @@ -26,7 +26,7 @@ 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(hasScopePseudo)), @@ -85,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 isSkipCache = hasDependsOnCurrentElement(subselect); + const shouldSkipCache = isContextDependent(subselect); const compiled = compileToken(subselect, copiedOptions, context); @@ -95,7 +95,7 @@ export const subselects: Record = { // If `compiled` is `trueFunc`, we can skip this. if (context && compiled !== boolbase.trueFunc) { - return isSkipCache + return shouldSkipCache ? (element) => { if (!next(element)) { return false; @@ -133,7 +133,7 @@ export const subselects: Record = { const hasOne = (element: ElementNode) => findOne(compiled, adapter.getChildren(element), options) !== null; - return isSkipCache + return shouldSkipCache ? (element) => next(element) && hasOne(element) : cacheParentResults(next, options, hasOne); }, From f6f03f158655550500eb10b154da0c46ee84c8b4 Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:25:48 +0100 Subject: [PATCH 08/13] ci: restore usual Dependabot auto-merge policy --- .github/workflows/dependabot-automerge.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml index 3213a9db..41481430 100644 --- a/.github/workflows/dependabot-automerge.yml +++ b/.github/workflows/dependabot-automerge.yml @@ -18,11 +18,11 @@ jobs: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs # Automatically merge semver-patch and semver-minor PRs - # Shared lint rule changes require manual review. - if: >- - ${{ !contains(steps.metadata.outputs.dependency-names, '@feedic/eslint-config') && - (steps.metadata.outputs.update-type == 'version-update:semver-minor' || - steps.metadata.outputs.update-type == 'version-update:semver-patch') }} + if: + "${{ steps.metadata.outputs.update-type == + 'version-update:semver-minor' || + steps.metadata.outputs.update-type == + 'version-update:semver-patch' }}" run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} From 0f5d9d164b23df26fe667dedbf81a02285f9c67b Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:29:37 +0100 Subject: [PATCH 09/13] test: format API cases with current Biome --- test/api.ts | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/test/api.ts b/test/api.ts index b98ba88e..74c8c997 100644 --- a/test/api.ts +++ b/test/api.ts @@ -133,18 +133,17 @@ 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.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( From 6d2e36096c5e5664763cd4f23f02fefe582cc3da Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:44:37 +0100 Subject: [PATCH 10/13] chore: remove unnecessary Node version file --- .node-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .node-version diff --git a/.node-version b/.node-version deleted file mode 100644 index 2bd5a0a9..00000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ -22 From 280424282b43fdb7bdf5ebfedbf83df48e6b4c78 Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Sun, 11 Oct 2026 00:57:01 +0100 Subject: [PATCH 11/13] chore: preserve direct selector quality comparisons --- src/helpers/selectors.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/helpers/selectors.ts b/src/helpers/selectors.ts index ee8d1f81..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, @@ -97,7 +98,9 @@ export function getQuality(token: InternalSelector): number { } case SelectorType.Pseudo: { return token.data - ? ["has", "contains", "icontains"].includes(token.name) + ? token.name === "has" || + token.name === "contains" || + token.name === "icontains" ? // Expensive in any case — run as late as possible. 0 : Array.isArray(token.data) From de180b0233aaa7c53dd0cd257a5f62a3b5c44233 Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:09:21 +0100 Subject: [PATCH 12/13] chore: consolidate lint rule explanations --- eslint.config.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 8d574007..944f128b 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -47,9 +47,9 @@ export default defineConfig([ }, }, eslintConfigBiome, - // This module uses parser DOM nodes, which do not implement browser traversal APIs. { rules: { + // This module uses parser DOM nodes, which do not implement browser traversal APIs. "unicorn/better-dom-traversing": "off", }, }, From 44d95fdc15622482311ade7db60159ca86c51de8 Mon Sep 17 00:00:00 2001 From: Felix Boehm <188768+fb55@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:18:48 +0100 Subject: [PATCH 13/13] refactor: clarify boolean and predicate names --- src/helpers/cache.ts | 6 +++--- src/pseudo-selectors/filters.ts | 11 +++++++---- src/pseudo-selectors/pseudos.ts | 4 ++-- test/sizzle.ts | 6 ++++-- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/helpers/cache.ts b/src/helpers/cache.ts index cda3a633..53bb3c2e 100644 --- a/src/helpers/cache.ts +++ b/src/helpers/cache.ts @@ -30,10 +30,10 @@ export function cacheParentResults( const resultCache = new WeakMap(); function addResultToCache(element: ElementNode) { - const isResult = isMatch(element); + const isMatched = isMatch(element); - resultCache.set(element, isResult); - return isResult; + resultCache.set(element, isMatched); + return isMatched; } return function cachedMatcher(element) { diff --git a/src/pseudo-selectors/filters.ts b/src/pseudo-selectors/filters.ts index 189c66e8..1ebaabae 100644 --- a/src/pseudo-selectors/filters.ts +++ b/src/pseudo-selectors/filters.ts @@ -43,10 +43,13 @@ export type Filter = ( compileToken?: CompileToken, ) => CompiledQuery; -function compileNth(isReverse: boolean, isOfType: boolean): Filter { +function compileNth( + shouldCountFromEnd: boolean, + shouldCountSameType: boolean, +): Filter { return function nth(next, rule, options, context, compileToken) { const { adapter, equals } = options; - const ofMatch = isOfType ? 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(isReverse: boolean, isOfType: boolean): Filter { const shouldCount = ofSelector ? (_element: ElementNode, sibling: ElementNode) => ofSelector(sibling) - : isOfType + : shouldCountSameType ? (element: ElementNode, sibling: ElementNode) => adapter.getName(sibling) === adapter.getName(element) : boolbase.trueFunc; - if (isReverse) { + if (shouldCountFromEnd) { return function nthLast(element) { if (ofSelector && !ofSelector(element)) return false; const siblings = adapter.getSiblings(element); diff --git a/src/pseudo-selectors/pseudos.ts b/src/pseudo-selectors/pseudos.ts index a8e93189..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)), ) ); }, diff --git a/test/sizzle.ts b/test/sizzle.ts index f62e138c..a68d44cd 100755 --- a/test/sizzle.ts +++ b/test/sizzle.ts @@ -39,9 +39,11 @@ describe("Sizzle", () => { // Select all expect(CSSselect.selectAll("*", document).length >= 30).toBe(true); const all = CSSselect.selectAll("*", document); - const isGood = all.every((element: AnyNode) => element.nodeType !== 8); + const hasNoComments = all.every( + (element: AnyNode) => element.nodeType !== 8, + ); // Select all elements, no comment nodes - expect(isGood).toBe(true); + expect(hasNoComments).toBe(true); // Element Selector t("html", ["html"]); // Element Selector