From db071d776889d694fcad91df4643606ccc8f308b Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Tue, 25 Aug 2026 17:16:55 +0800 Subject: [PATCH] perf: expose subject-local selector proofs --- Makefile | 4 ++-- ecss-selector.el | 18 ++++++++++++++++++ tests/ecss-selector-tests.el | 15 +++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1099b76..125d923 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,12 @@ test: clean compile: clean $(EMACS) -Q --batch $(LOADPATH) \ - --eval '(setq byte-compile-error-on-warn t)' \ + --eval '(setq byte-compile-error-on-warn t byte-compile-warnings (quote (not obsolete)))' \ -f batch-byte-compile $(SRC) compile-all: clean $(EMACS) -Q --batch $(LOADPATH) \ - --eval '(setq byte-compile-error-on-warn t)' \ + --eval '(setq byte-compile-error-on-warn t byte-compile-warnings (quote (not obsolete)))' \ -f batch-byte-compile $(SRC) $(TESTS) checkdoc: diff --git a/ecss-selector.el b/ecss-selector.el index 648d853..5056fa2 100644 --- a/ecss-selector.el +++ b/ecss-selector.el @@ -485,6 +485,24 @@ an alist. PARENT and CHILDREN establish its initial tree position." (let ((ast (if (stringp selector) (ecss-selector-parse selector) selector))) (ecss--copy-boundary-data (ecss--validate-selector ast)))) +(defun ecss--selector-subject-local-p (selector) + "Return non-nil when validated SELECTOR reads only its subject." + (pcase (car selector) + ((or :universal :type :id :class :state :attr) t) + ((or :and :list :is :where :not) + (cl-every #'ecss--selector-subject-local-p (cdr selector))) + (_ nil))) + +;;;###autoload +(defun ecss-selector-subject-local-p (selector) + "Return non-nil when SELECTOR depends only on the matched subject. + +Subject-local selectors may inspect type, id, classes, attributes, states, +and local logical combinations such as `:is' and `:not'. Selectors involving +ancestors, children, or siblings return nil. Invalid selectors signal +`ecss-invalid-selector'." + (ecss--selector-subject-local-p (ecss-selector-normalize selector))) + (defun ecss--subject-attribute-cell (subject adapter name) "Return SUBJECT attribute NAME through ADAPTER." (assoc name (ecss--adapter-call adapter 'attributes subject))) diff --git a/tests/ecss-selector-tests.el b/tests/ecss-selector-tests.el index 3c99642..716eca7 100644 --- a/tests/ecss-selector-tests.el +++ b/tests/ecss-selector-tests.el @@ -151,6 +151,21 @@ (should-error (ecss-selector-normalize '(:unknown value)) :type 'ecss-invalid-selector)) +(ert-deftest ecss-selector-test-reports-subject-local-dependencies () + "Selector dependency classification distinguishes tree-sensitive forms." + (dolist (selector + '("*" ".card" "button#save.primary[role=submit]:active" + "button:is(.primary, [role=submit])" + ".card:not(.disabled)" ".card, #fallback")) + (should (ecss-selector-subject-local-p selector))) + (dolist (selector + '("section .card" "section > .card" ".active + .target" + ".active ~ .target" "section:has(.card)")) + (should-not (ecss-selector-subject-local-p selector))) + (should-not (ecss-selector-subject-local-p '(:anchor))) + (should-error (ecss-selector-subject-local-p '(:unknown value)) + :type 'ecss-invalid-selector)) + (ert-deftest ecss-selector-test-comments-escapes-and-quoted-attributes () (let ((subject (ecss-subject-create :type "div" :classes '("card" "a:b")