HS: parser fixes — parenthesized commands + add error + class-name depth (+3 tests)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s

- parse-on-feat: event-vars paren check now restores position and returns empty
  list when the first token after '(' is a keyword (command starter). Previously
  '(log me)' was consumed as event variable names instead of a parenthesized
  command, silently dropping the command body and returning empty innerHTML.
  Fixes 'can support parenthesized commands and features'.

- parse-add-cmd: true-fallback now throws instead of returning nil when no 'to'
  keyword follows the expression. Makes 'add - to' and similar invalid add forms
  throw a parse error, satisfying assert-throws in 'basic parse error messages
  work' and '_hyperscript() evaluate API still throws on first error'.

- read-class-name: '(' and ')' now only allowed inside '[...]' bracket groups
  (depth > 0). Previously allowing them at top level caused '.innerHTML)' at the
  end of a possessive expression to be consumed into the class token, producing
  'innerHTML))' as a bogus property name. Tailwind classes like
  'group-[:nth-of-type(3)_&]:block' still tokenize correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 17:38:29 +00:00
parent d9b7e1e392
commit 20e23d233c
4 changed files with 58 additions and 14 deletions

View File

@@ -1164,7 +1164,9 @@
(let
((tgt (parse-expr)))
(list (quote add-value) value tgt))
nil))))))
(error
(str
"Invalid 'add' syntax: expected a class (.foo), attribute, or expression with 'to'"))))))))
(define
parse-remove-cmd
(fn
@@ -2973,7 +2975,7 @@
(let
((of-filter (when (and (= event-name "mutation") (match-kw "of")) (cond ((and (= (tp-type) "ident") (or (= (tp-val) "attributes") (= (tp-val) "childList") (= (tp-val) "characterData"))) (let ((nm (tp-val))) (do (adv!) (dict "type" nm)))) ((= (tp-type) "attr") (let ((attrs (list (tp-val)))) (do (adv!) (define collect-or! (fn () (when (match-kw "or") (cond ((= (tp-type) "attr") (do (set! attrs (append attrs (list (tp-val)))) (adv!) (collect-or!))) (true (set! p (- p 1))))))) (collect-or!) (dict "type" "attrs" "attrs" attrs)))) (true nil)))))
(let
((event-vars (if (= (tp-type) "paren-open") (do (adv!) (define ev-coll (fn () (cond ((or (= (tp-type) "paren-close") (= (tp-type) "eof")) (do (when (= (tp-type) "paren-close") (adv!)) (list))) ((or (= (tp-type) "ident") (= (tp-type) "keyword")) (let ((nm (tp-val))) (adv!) (cons nm (ev-coll)))) (true (do (adv!) (ev-coll)))))) (ev-coll)) (list))))
((event-vars (if (= (tp-type) "paren-open") (let ((saved-p p)) (do (adv!) (if (= (tp-type) "keyword") (do (set! p saved-p) (list)) (do (define ev-coll (fn () (cond ((or (= (tp-type) "paren-close") (= (tp-type) "eof")) (do (when (= (tp-type) "paren-close") (adv!)) (list))) ((or (= (tp-type) "ident") (= (tp-type) "keyword")) (let ((nm (tp-val))) (adv!) (cons nm (ev-coll)))) (true (do (adv!) (ev-coll)))))) (ev-coll))))) (list))))
(let
((flt (if (= (tp-type) "bracket-open") (do (adv!) (let ((f (parse-expr))) (if (= (tp-type) "bracket-close") (adv!) nil) f)) nil)))
(let