Don't freeze Emacs on colour codes in sccs-mode

* lisp/textmodes/css-mode.el (css--font-lock-keywords): Don't
freeze Emacs on #ffffff #ffffff, and be more strict in parsing
selectors (bug#53203).
This commit is contained in:
Lars Ingebrigtsen 2022-05-15 14:13:14 +02:00
parent b26574d7d7
commit 0da7689b16
4 changed files with 166 additions and 21 deletions

View file

@ -928,6 +928,32 @@ cannot be completed sensibly: `custom-ident',
(defface css-proprietary-property '((t :inherit (css-property italic)))
"Face to use for vendor-specific properties.")
(defun css--selector-regexp (sassy)
(concat
"\\(?:"
(if (not sassy)
"[-_%*#.>[:alnum:]]+"
;; Same as for non-sassy except we do want to allow { and }
;; chars in selectors in the case of #{$foo}
;; variable interpolation!
(concat "\\(?:[-_%*#.>[:alnum:]]*" scss--hash-re
"\\|[-_%*#.>[:alnum:]]+\\)"))
;; Even though pseudo-elements should be prefixed by ::, a
;; single colon is accepted for backward compatibility.
"\\(?:\\(:" (regexp-opt (append css-pseudo-class-ids
css-pseudo-element-ids)
t)
"\\|::" (regexp-opt css-pseudo-element-ids t) "\\)\\)?"
;; Braces after selectors.
"\\(?:\\[[^]\n]+\\]\\)?"
;; Parentheses after selectors.
"\\(?:([^)]+)\\)?"
;; Main bit over. But perhaps just [target]?
"\\|\\[[^]\n]+\\]"
;; :root, ::marker and the like.
"\\|::?[[:alnum:]]+\\(?:([^)]+)\\)?"
"\\)"))
(defun css--font-lock-keywords (&optional sassy)
`((,(concat "!\\s-*" (regexp-opt css--bang-ids))
(0 font-lock-builtin-face))
@ -948,28 +974,16 @@ cannot be completed sensibly: `custom-ident',
;; selector between [...] should simply not be highlighted.
(,(concat
"^[ \t]*\\("
(if (not sassy)
;; We don't allow / as first char, so as not to
;; take a comment as the beginning of a selector.
"[^@/:{}() \t\n][^:{}()]*"
;; Same as for non-sassy except we do want to allow { and }
;; chars in selectors in the case of #{$foo}
;; variable interpolation!
(concat "\\(?:" scss--hash-re
"\\|[^@/:{}() \t\n#]\\)"
"[^:{}()#]*\\(?:" scss--hash-re "[^:{}()#]*\\)*"))
;; Even though pseudo-elements should be prefixed by ::, a
;; single colon is accepted for backward compatibility.
"\\(?:\\(:" (regexp-opt (append css-pseudo-class-ids
css-pseudo-element-ids)
t)
"\\|::" (regexp-opt css-pseudo-element-ids t) "\\)"
"\\(?:([^)]+)\\)?"
(if (not sassy)
"[^:{}()\n]*"
(concat "[^:{}()\n#]*\\(?:" scss--hash-re "[^:{}()\n#]*\\)*"))
;; We have at least one selector.
(css--selector-regexp sassy)
;; And then possibly more.
"\\(?:"
;; Separators between selectors.
"[ \n\t,+~>]+"
(css--selector-regexp sassy)
"\\)*"
"\\)\\(?:\n[ \t]*\\)*{")
;; And then a brace.
"\\)[ \n\t]*{")
(1 'css-selector keep))
;; In the above rule, we allow the open-brace to be on some subsequent
;; line. This will only work if we properly mark the intervening text

View file

@ -0,0 +1,56 @@
#firstname
*
p
p.intro
div, p
div p
div > p
div + p
p ~ ul
[target]
[target=_blank]
[title~=flower]
[lang|=en]
a[href^="https"]
a[href$=".pdf"]
a[href*="w3schools"]
a:active
p::after
p::before
input:checked
input:default
input:disabled
p:empty
input:enabled
p:first-child
p::first-letter
p::first-line
p:first-of-type
input:focus
:fullscreen
a:hover
input:in-range
input:indeterminate
input:invalid
p:lang(it)
p:last-child
p:last-of-type
a:link
::marker
:not(p)
p:nth-child(2)
p:nth-last-child(2)
p:nth-last-of-type(2)
p:nth-of-type(2)
p:only-of-type
p:only-child
input:optional
input:out-of-range
input:read-only
input:read-write
input:required
:root
::selection
#news:target
input:valid
a:visited

View file

@ -0,0 +1,6 @@
p.#{$name} var
p.#{$name}:active var
p.#{$name}::after var
f.#{$bar}::after p::after
p.#{$name} f.#{$bar} k.var #{$bar} #{$bar}
p.#{$name}

View file

@ -419,5 +419,74 @@
(indent-region (point-min) (point-max))
(should (equal (buffer-string) orig)))))
(ert-deftest css-mode-test-selectors ()
(let ((selectors
(with-temp-buffer
(insert-file-contents (ert-resource-file "css-selectors.txt"))
(string-lines (buffer-string)))))
(with-suppressed-warnings ((interactive font-lock-debug-fontif))
(dolist (selector selectors)
(with-temp-buffer
(css-mode)
(insert selector " {\n}\n")
(font-lock-debug-fontify)
(goto-char (point-min))
(unless (eq (get-text-property (point) 'face)
'css-selector)
(should-not (format "Didn't recognize %s as a selector"
(buffer-substring-no-properties
(point) (line-end-position)))))))
;; Test many selectors.
(dolist (selector selectors)
(with-temp-buffer
(css-mode)
(insert selector " ")
(dotimes (_ (random 5))
(insert (seq-random-elt '(" , " " > " " + "))
(seq-random-elt selectors)))
(insert "{\n}\n")
(font-lock-debug-fontify)
(goto-char (point-min))
(unless (eq (get-text-property (point) 'face)
'css-selector)
(should-not (format "Didn't recognize %s as a selector"
(buffer-substring-no-properties
(point) (line-end-position)))))))
;; Test wrong separators.
(dolist (selector selectors)
(with-temp-buffer
(css-mode)
(insert selector " ")
(dotimes (_ (1+ (random 5)))
(insert (seq-random-elt '("=" " @ "))
(seq-random-elt selectors)))
(insert "{\n}\n")
(font-lock-debug-fontify)
(goto-char (point-min))
(when (eq (get-text-property (point) 'face)
'css-selector)
(should-not (format "Recognized %s as a selector"
(buffer-substring-no-properties
(point) (line-end-position))))))))))
(ert-deftest scss-mode-test-selectors ()
(let ((selectors
(with-temp-buffer
(insert-file-contents (ert-resource-file "scss-selectors.txt"))
(string-lines (buffer-string)))))
(with-suppressed-warnings ((interactive font-lock-debug-fontif))
(dolist (selector selectors)
(with-temp-buffer
(scss-mode)
(insert selector " {\n}\n")
(font-lock-debug-fontify)
(goto-char (point-min))
(unless (eq (get-text-property (point) 'face)
'css-selector)
(should-not (format "Didn't recognize %s as a selector"
(buffer-substring-no-properties
(point) (line-end-position))))))))))
(provide 'css-mode-tests)
;;; css-mode-tests.el ends here