Subject: bug#69714: 30.0.50; ert-font-lock doesn't handle list of faces

From: Vladimir Kazanov <[email-masked]>
Date: Wed, 13 Mar 2024 18:20:23 +0000

Yeah, this is unintentional but I like it. "nil" is happily parsed by
both the (read) call and the guarding regex.

There's a little thing that needs to be fixed - avoid turning 'nil
into '(nil) when comparing lists. It works but for the wrong reason.
Anyway, I'll update the docs to suggest using nil and !nil in
assertions, and also add a test.

The patch looks ready. I'll wait for a few days and if nobody notices
I will give the maintainers a shout.

On Wed, 13 Mar 2024 at 17:49, Troy Brown <[email-masked]> wrote:
>
> The new patch looks good to me.
>
> One other thing I thought I'd mention.  I have places in my test where
> I want to verify that there is no font lock face.  I've been able to
> specify "nil" for the face and that works perfectly to check this.  I
> thought I'd mention this because I wasn't sure if it was intentional
> behavior, but I do find that useful.
>
>
> Thanks,
>
> Troy.
>
> On Wed, Mar 13, 2024 at 1:04 PM Vladimir Kazanov <[email-masked]> wrote:
> >
> > Thanks a lot!
> >
> > The suggestions really do make sense.
> >
> > Here's the final integrated patch, complete with updated tests and
> > docs. If you're fine with it then I'll ask somebody to install it on
> > master.
> >
> > PS I've got to write an additional announcement in the main mailing
> > list inviting people to check the updated version out.
> >
> > On Wed, 13 Mar 2024 at 16:14, Troy Brown <[email-masked]> wrote:
> > >
> > > Hi Vlad, sorry for the delayed response.
> > >
> > > I haven't pushed my change which uses this package yet, as I was
> > > struggling to get it working and didn't want to push failing tests.  I
> > > just discovered the package and was working on a regression test for a
> > > bug fix involving font locking.  This seemed like the perfect reason
> > > to use your package.  At the moment I only have this one
> > > work-in-progress test, but I expect to use it more going forward.
> > >
> > > I did check out your patch and for my immediate needs, it worked
> > > perfectly.  Thanks!  Additionally, I did experiment a little with the
> > > multi-caret functionality, which is nice as I have a use for that.  I
> > > also experimented with the negation functionality (although I don't
> > > have an immediate need for that), and did notice a couple things.  The
> > > first was that the assertion would be ignored if there was a space
> > > between the negation symbol and the face.  Also, if the actual and
> > > expected faces didn't match and the negation flag was being used, it
> > > acted like the negation was not indicated at all and failed the test.
> > > I've included a diff below containing the changes I made which seemed
> > > to address those concerns.
> > >
> > > diff --git a/lisp/emacs-lisp/ert-font-lock.el b/lisp/emacs-lisp/ert-font-lock.el
> > > index 06c90add9d3..1a5fe96fb09 100644
> > > --- a/lisp/emacs-lisp/ert-font-lock.el
> > > +++ b/lisp/emacs-lisp/ert-font-lock.el
> > > @@ -61,6 +61,7 @@ ert-font-lock--assertion-line-re
> > >     (group (zero-or-more (seq "^" (zero-or-more whitespace))))
> > >     ;; optional negation of the face specification
> > >     (group (optional "!"))
> > > +   (zero-or-more whitespace)
> > >     ;; face symbol name or a list of symbols
> > >     (group (or (regexp ert-font-lock--face-symbol-re)
> > >                (regexp ert-font-lock--face-symbol-list-re))))
> > > @@ -354,7 +355,7 @@ ert-font-lock--check-faces
> > >        (when (symbolp expected-face)
> > >          (setq expected-face (list expected-face)))
> > >
> > > -      (when (not (equal actual-face expected-face))
> > > +      (when (and (not negation) (not (equal actual-face expected-face)))
> > >          (ert-fail
> > >           (list (format "Expected face %S, got %S on line %d column %d"
> > >                         expected-face actual-face line-checked column-checked)
> > >
> > >
> > > Thanks,
> > >
> > > Troy.
> > >
> > > On Tue, Mar 12, 2024 at 4:47 PM Vladimir Kazanov <[email-masked]> wrote:
> > > >
> > > > Hi,
> > > >
> > > > I've attached a patch that handles face lists, fails on files without
> > > > assertions and expands the parser a bit to support multiple carets per
> > > > line.
> > > >
> > > > For faces it does the following:
> > > >
> > > > 1. Turn symbols into single element lists.
> > > > 2. Parses face lists from the assertions.
> > > > 3. Compare face lists using equas.
> > > >
> > > > Could you please check if this works for you?
> > > >
> > > > Thanks
> > > >
> > > > On Mon, 11 Mar 2024 at 08:36, Vladimir Kazanov <[email-masked]> wrote:
> > > > >
> > > > > Hi,
> > > > >
> > > > > Thanks for reporting this! I have a bunch of ert-font-lock
> > > > > improvements in my local repo getting ready for submission, and can
> > > > > look into your suggestions as well.
> > > > >
> > > > > Do you have your unit test code somewhere in a public repo? It'd be
> > > > > great to think of further improvements to support your use case.
> > > > >
> > > > > Thanks,
> > > > > Vlad
> > > > >
> > > > > On Sun, 10 Mar 2024 at 20:33, Troy Brown <[email-masked]> wrote:
> > > > > >
> > > > > > I'm trying to use this package to test out my tree-sitter mode, but am
> > > > > > running into an issue with lists of faces.  It's possible that the
> > > > > > face for a location in the buffer will contain a list of 1 or more
> > > > > > faces.  For example, when I use the ":override 'prepend" keyword in
> > > > > > the call to treesit-font-lock-rules, even if only a single face is
> > > > > > specified for the rule that matches that section of the buffer, this
> > > > > > will result in a list of one entry (i.e., "(face-name)").
> > > > > >
> > > > > > When this happens, ert-font-lock fails to recognize that this matches
> > > > > > the face "face-name" (e.g., "^ face-name" will fail to match in this
> > > > > > case).  I feel the tool should recognize a list containing a single
> > > > > > face as matching the face.  Even worse however, it appears
> > > > > > ert-font-lock doesn't support a list of faces in the comment.  I tried
> > > > > > to work around the original issue by using "^ (face-name)", but the
> > > > > > tool silently ignores this, as it doesn't match the internal regular
> > > > > > expression (which ended up allowing my test to pass without actually
> > > > > > checking anything).
> > > > > >
> > > > > > I can't figure out a way to use this tool in its current state due to
> > > > > > its lack of support for a list of faces.  Also, I find that since it
> > > > > > silently ignores incorrect comment syntax (e.g., "^face-name", "^
> > > > > > (face-name)"), it gives a false illusion that it's actually performing
> > > > > > those checks (and the checks are passing), when it's really just
> > > > > > ignoring them.  Maybe any comment line starting with a "^" or "<-"
> > > > > > should be considered an assertion check and to fail if the rest of the
> > > > > > syntax is not as expected.  Maybe it should also fail the test if no
> > > > > > assertion checks are found in a source file or string.
> > > > > >
> > > > > > Even if the tool would allow a list of a single face to match the
> > > > > > supplied face in the comment, I think it should also allow for
> > > > > > multiple faces to be listed in the comment.  I have other places where
> > > > > > multiple faces are used (e.g., "(font-lock-constant-face
> > > > > > font-lock-variable-name-face)" to highlight a constant variable),
> > > > > > which would not be testable with the current state of the package.
> > > > > >
> > > > > >
> > > > > >
> > > > >
> > > > >
> > > > > --
> > > > > Regards,
> > > > >
> > > > > Vladimir Kazanov
> > > >
> > > >
> > > >
> > > > --
> > > > Regards,
> > > >
> > > > Vladimir Kazanov
> >
> >
> >
> > --
> > Regards,
> >
> > Vladimir Kazanov

-- 
Regards,

Vladimir Kazanov
From 402688f227eb786fe81ad3799f97b51cd6f7a693 Mon Sep 17 00:00:00 2001
From: Vladimir Kazanov <[email-masked]>
Date: Tue, 12 Mar 2024 11:14:54 +0000
Subject: [PATCH] Improve ert-font-lock assertion parser (Bug#69714)

Fail on files with no assertions, parser now accepts multiple
carets per line and face lists:
* lisp/emacs-lisp/ert-font-lock.el: Assertion parser fix.
* test/lisp/emacs-lisp/ert-font-lock-resources/no-asserts.js:
* test/lisp/emacs-lisp/ert-font-lock-tests.el: Update unit tests.
* doc/misc/ert.texi: Update documentation.
---
 doc/misc/ert.texi                             |  45 +++++-
 lisp/emacs-lisp/ert-font-lock.el              |  73 +++++++--
 .../ert-font-lock-resources/no-asserts.js     |   2 +
 test/lisp/emacs-lisp/ert-font-lock-tests.el   | 153 +++++++++++++++---
 4 files changed, 228 insertions(+), 45 deletions(-)
 create mode 100644 test/lisp/emacs-lisp/ert-font-lock-resources/no-asserts.js

diff --git a/doc/misc/ert.texi b/doc/misc/ert.texi
index bd2ad495142..8767de71496 100644
--- a/doc/misc/ert.texi
+++ b/doc/misc/ert.texi
@@ -951,11 +951,13 @@ Syntax Highlighting Tests
 @code{ert-font-lock} package makes it possible to introduce unit tests
 checking face assignment.  Test assertions are included in code-level
 comments directly and can be read either from inline strings or files.
+The parser expects the input string to contain at least one assertion.

 Test assertion parser extracts tests from comment-only lines.  Every
-comment assertion line starts either with a caret (@samp{^}) or an
-arrow (@samp{<-}).  A caret/arrow should be followed immediately by the
-name of a face to be checked.
+comment assertion line starts either with a caret (@samp{^}) or an arrow
+(@samp{<-}).  A single caret/arrow or carets should be followed
+immediately by the name of a face or a list of faces to be checked
+against the @code{:face} property at point.

 The test then checks if the first non-assertion column above the caret
 contains a face expected by the assertion:
@@ -967,10 +969,43 @@ Syntax Highlighting Tests
 //               ^ font-lock-punctuation-face
 // this is not an assertion, it's just a comment
 //   ^ font-lock-comment-face
+
+// multiple carets per line
+// ^^^^     ^    ^ font-lock-comment-face
+@end example
+
+Both symbol-only @code{:face} property values and assertion face values
+are normalized to single element lists so assertions below are
+equivalent:
+
+@example
+// single
+// ^ font-lock-comment-face
+// single
+// ^ (font-lock-comment-face)
+@end example
+
+Assertions can be negated:
+
+@example
+var variable = 11;
+//  ^ !font-lock-comment-face
+@end example
+
+It is possible to specify face lists in assertions:
+
+@example
+// TODO
+// ^^^^ (font-lock-comment-face hl-todo)
+     var test = 1;
+// ^    ()
+// ^    nil
+//   negation works as expected
+//   ^  !nil
 @end example

-The arrow means that the first non-empty column of the assertion line
-will be used for the check:
+The arrow (@samp{<-}) means that the first non-empty column of the
+assertion line will be used for the check:

 @example
 var variable = 1;
diff --git a/lisp/emacs-lisp/ert-font-lock.el b/lisp/emacs-lisp/ert-font-lock.el
index 29114712f92..e77c8945dc3 100644
--- a/lisp/emacs-lisp/ert-font-lock.el
+++ b/lisp/emacs-lisp/ert-font-lock.el
@@ -39,16 +39,33 @@
 (require 'newcomment)
 (require 'pcase)

-(defconst ert-font-lock--assertion-re
+(defconst ert-font-lock--face-symbol-re
+  (rx (one-or-more (or alphanumeric "-" "_" ".")))
+  "A face symbol matching regex.")
+
+(defconst ert-font-lock--face-symbol-list-re
+  (rx "("
+      (* whitespace)
+      (one-or-more
+       (seq (regexp ert-font-lock--face-symbol-re)
+            (* whitespace)))
+      ")")
+  "A face symbol list matching regex.")
+
+(defconst ert-font-lock--assertion-line-re
   (rx
-   ;; column specifiers
+   ;; leading column assertion (arrow/caret)
    (group (or "^" "<-"))
-   (one-or-more " ")
+   (zero-or-more whitespace)
+   ;; possible to have many carets on an assertion line
+   (group (zero-or-more (seq "^" (zero-or-more whitespace))))
    ;; optional negation of the face specification
    (group (optional "!"))
-   ;; face symbol name
-   (group (one-or-more (or alphanumeric "-" "_" "."))))
-  "An ert-font-lock assertion regex.")
+   (zero-or-more whitespace)
+   ;; face symbol name or a list of symbols
+   (group (or (regexp ert-font-lock--face-symbol-re)
+              (regexp ert-font-lock--face-symbol-list-re))))
+  "An ert-font-lock assertion line regex.")

 (defun ert-font-lock--validate-major-mode (mode)
   "Validate if MODE is a valid major mode."
@@ -212,7 +229,7 @@ ert-font-lock--line-assertion-p
   (save-excursion
     (beginning-of-line)
     (skip-syntax-forward " ")
-    (re-search-forward ert-font-lock--assertion-re
+    (re-search-forward ert-font-lock--assertion-line-re
                        (line-end-position) t 1)))

 (defun ert-font-lock--goto-first-char ()
@@ -252,8 +269,8 @@ ert-font-lock--parse-comments
           (throw 'nextline t))

-        ;; Collect the assertion
-        (when (re-search-forward ert-font-lock--assertion-re
+        ;; Collect the first line assertion (caret or arrow)
+        (when (re-search-forward ert-font-lock--assertion-line-re
                                  (line-end-position) t 1)

           (unless (> linetocheck -1)
@@ -266,21 +283,38 @@ ert-font-lock--parse-comments
                                      (- (match-beginning 1) (line-beginning-position))
                                    (ert-font-lock--get-first-char-column)))
                  ;; negate the face?
-                 (negation (string-equal (match-string-no-properties 2) "!"))
+                 (negation (string-equal (match-string-no-properties 3) "!"))
                  ;; the face that is supposed to be in the position specified
-                 (face (match-string-no-properties 3)))
+                 (face (read (match-string-no-properties 4))))

+            ;; Collect the first assertion on the line
             (push (list :line-checked linetocheck
                         :line-assert curline
                         :column-checked column-checked
                         :face face
                         :negation negation)
-                  tests))))
+                  tests)
+
+            ;; Collect all the other line carets (if present)
+            (goto-char (match-beginning 2))
+            (while (equal (following-char) ?^)
+              (setq column-checked (- (point) (line-beginning-position)))
+              (push (list :line-checked linetocheck
+                          :line-assert curline
+                          :column-checked column-checked
+                          :face face
+                          :negation negation)
+                    tests)
+              (forward-char)
+              (skip-syntax-forward " ")))))

       ;; next line
       (setq curline (1+ curline))
       (forward-line 1))

+    (unless tests
+      (user-error "No test assertions found"))
+
     (reverse tests)))

 (defun ert-font-lock--point-at-line-and-column (line column)
@@ -307,21 +341,30 @@ ert-font-lock--check-faces
     (let* ((line-checked (plist-get test :line-checked))
            (line-assert (plist-get test :line-assert))
            (column-checked (plist-get test :column-checked))
-           (expected-face (intern (plist-get test :face)))
+           (expected-face (plist-get test :face))
            (negation (plist-get test :negation))

            (actual-face (get-text-property (ert-font-lock--point-at-line-and-column line-checked column-checked) 'face))
            (line-str (ert-font-lock--get-line line-checked))
            (line-assert-str (ert-font-lock--get-line line-assert)))

-      (when (not (eq actual-face expected-face))
+      ;; normalize both expected and resulting face - these can be
+      ;; either symbols, nils or lists of symbols
+      (when (not (listp actual-face))
+        (setq actual-face (list actual-face)))
+      (when (not (listp expected-face))
+        (setq expected-face (list expected-face)))
+
+      ;; fail when lists are not 'equal and the assertion is *not negated*
+      (when (and (not negation) (not (equal actual-face expected-face)))
         (ert-fail
          (list (format "Expected face %S, got %S on line %d column %d"
                        expected-face actual-face line-checked column-checked)
                :line line-str
                :assert line-assert-str)))

-      (when (and negation (eq actual-face expected-face))
+      ;; fail when lists are 'equal and the assertion is *negated*
+      (when (and negation (equal actual-face expected-face))
         (ert-fail
          (list (format "Did not expect face %S face on line %d, column %d"
                        actual-face line-checked column-checked)
diff --git a/test/lisp/emacs-lisp/ert-font-lock-resources/no-asserts.js b/test/lisp/emacs-lisp/ert-font-lock-resources/no-asserts.js
new file mode 100644
index 00000000000..5eae9af212f
--- /dev/null
+++ b/test/lisp/emacs-lisp/ert-font-lock-resources/no-asserts.js
@@ -0,0 +1,2 @@
+var abc = function(d) {
+};
diff --git a/test/lisp/emacs-lisp/ert-font-lock-tests.el b/test/lisp/emacs-lisp/ert-font-lock-tests.el
index e0ba1e949b2..fa2e5dc4db7 100644
--- a/test/lisp/emacs-lisp/ert-font-lock-tests.el
+++ b/test/lisp/emacs-lisp/ert-font-lock-tests.el
@@ -138,13 +138,24 @@ test-line-comment-p--c
                              (forward-line)
                              (should (ert-font-lock--line-comment-p))))

+(ert-deftest test-parse-comments--no-assertion-error ()
+  (let* ((str "
+not_an_assertion
+random_symbol
+"))
+    (with-temp-buffer
+      (insert str)
+      (javascript-mode)
+
+      (should-error (ert-font-lock--parse-comments) :type 'user-error))))
+
 (ert-deftest test-parse-comments--single-line-error ()
   (let* ((str "// ^ face.face1"))
     (with-temp-buffer
       (insert str)
       (javascript-mode)

-      (should-error (ert-font-lock--parse-comments)))))
+      (should-error (ert-font-lock--parse-comments) :type 'user-error))))

 (ert-deftest test-parse-comments--single-line-single-caret ()
   (let* ((str "
@@ -159,7 +170,46 @@ test-parse-comments--single-line-single-caret
       (setq asserts (ert-font-lock--parse-comments))
       (should (eql (length asserts) 1))
       (should (equal (car asserts)
-                     '(:line-checked 2 :line-assert 3 :column-checked 3 :face "face.face1" :negation nil))))))
+                     '(:line-checked 2 :line-assert 3 :column-checked 3 :face face.face1 :negation nil))))))
+
+(ert-deftest test-parse-comments--single-line-many-carets ()
+  (let* ((str "
+multiplecarets
+//^^^ ^^ ^ face.face1
+")
+         asserts)
+    (with-temp-buffer
+      (insert str)
+      (javascript-mode)
+
+      (setq asserts (ert-font-lock--parse-comments))
+      (should (eql (length asserts) 6))
+      (should (equal asserts
+                     '((:line-checked 2 :line-assert 3 :column-checked 2 :face face.face1 :negation nil)
+                       (:line-checked 2 :line-assert 3 :column-checked 3 :face face.face1 :negation nil)
+                       (:line-checked 2 :line-assert 3 :column-checked 4 :face face.face1 :negation nil)
+                       (:line-checked 2 :line-assert 3 :column-checked 6 :face face.face1 :negation nil)
+                       (:line-checked 2 :line-assert 3 :column-checked 7 :face face.face1 :negation nil)
+                       (:line-checked 2 :line-assert 3 :column-checked 9 :face face.face1 :negation nil)))))))
+
+(ert-deftest test-parse-comments--face-list ()
+  (let* ((str "
+facelist
+// ^ (face1 face2)
+// ^ !(face3 face4)
+// ^ (face5)
+")
+         asserts)
+    (with-temp-buffer
+      (insert str)
+      (javascript-mode)
+
+      (setq asserts (ert-font-lock--parse-comments))
+      (should (eql (length asserts) 3))
+      (should (equal asserts
+                     '((:line-checked 2 :line-assert 3 :column-checked 3 :face (face1 face2) :negation nil)
+                       (:line-checked 2 :line-assert 4 :column-checked 3 :face (face3 face4) :negation t)
+                       (:line-checked 2 :line-assert 5 :column-checked 3 :face (face5) :negation nil)))))))

 (ert-deftest test-parse-comments--caret-negation ()
   (let* ((str "
@@ -175,11 +225,11 @@ test-parse-comments--caret-negation
       (setq asserts (ert-font-lock--parse-comments))
       (should (eql (length asserts) 2))
       (should (equal asserts
-                     '((:line-checked 2 :line-assert 3 :column-checked 3 :face "face" :negation t)
-                       (:line-checked 2 :line-assert 4 :column-checked 3 :face "face" :negation nil)))))))
+                     '((:line-checked 2 :line-assert 3 :column-checked 3 :face face :negation t)
+                       (:line-checked 2 :line-assert 4 :column-checked 3 :face face :negation nil)))))))

-(ert-deftest test-parse-comments--single-line-multiple-carets ()
+(ert-deftest test-parse-comments--single-line-multiple-assert-lines ()
   (let* ((str "
 first
 // ^ face1
@@ -196,12 +246,12 @@ test-parse-comments--single-line-multiple-carets
       (setq asserts (ert-font-lock--parse-comments))
       (should (eql (length asserts) 4))
       (should (equal asserts
-                     '((:line-checked 2 :line-assert 3 :column-checked 3 :face "face1" :negation nil)
-                       (:line-checked 2 :line-assert 4 :column-checked 7 :face "face.face2" :negation nil)
-                       (:line-checked 2 :line-assert 5 :column-checked 7 :face "face-face.face3" :negation nil)
-                       (:line-checked 2 :line-assert 6 :column-checked 7 :face "face_face.face4" :negation nil)))))))
+                     '((:line-checked 2 :line-assert 3 :column-checked 3 :face face1 :negation nil)
+                       (:line-checked 2 :line-assert 4 :column-checked 7 :face face.face2 :negation nil)
+                       (:line-checked 2 :line-assert 5 :column-checked 7 :face face-face.face3 :negation nil)
+                       (:line-checked 2 :line-assert 6 :column-checked 7 :face face_face.face4 :negation nil)))))))

-(ert-deftest test-parse-comments--multiple-line-multiple-carets ()
+(ert-deftest test-parse-comments--multiple-line-multiple-assert-lines ()
   (let* ((str "
 first
 // ^ face1
@@ -218,9 +268,9 @@ test-parse-comments--multiple-line-multiple-carets
       (setq asserts (ert-font-lock--parse-comments))
       (should (eql (length asserts) 3))
       (should (equal asserts
-                     '((:line-checked 2  :line-assert 3 :column-checked 3 :face "face1" :negation nil)
-                       (:line-checked 4  :line-assert 5 :column-checked 3 :face "face2" :negation nil)
-                       (:line-checked 4  :line-assert 6 :column-checked 5 :face "face3" :negation nil)))))))
+                     '((:line-checked 2  :line-assert 3 :column-checked 3 :face face1 :negation nil)
+                       (:line-checked 4  :line-assert 5 :column-checked 3 :face face2 :negation nil)
+                       (:line-checked 4  :line-assert 6 :column-checked 5 :face face3 :negation nil)))))))

 (ert-deftest test-parse-comments--arrow-single-line-single ()
@@ -236,7 +286,7 @@ test-parse-comments--arrow-single-line-single
       (setq asserts (ert-font-lock--parse-comments))
       (should (eql (length asserts) 1))
       (should (equal (car asserts)
-                     '(:line-checked 2 :line-assert 3 :column-checked 0 :face "face1" :negation nil))))))
+                     '(:line-checked 2 :line-assert 3 :column-checked 0 :face face1 :negation nil))))))

 (ert-deftest test-parse-comments-arrow-multiple-line-single ()
@@ -254,9 +304,9 @@ test-parse-comments-arrow-multiple-line-single
       (setq asserts (ert-font-lock--parse-comments))
       (should (eql (length asserts) 3))
       (should (equal asserts
-                     '((:line-checked 2 :line-assert 3 :column-checked 0 :face "face1" :negation nil)
-                       (:line-checked 2 :line-assert 4 :column-checked 2 :face "face2" :negation nil)
-                       (:line-checked 2 :line-assert 5 :column-checked 4 :face "face3" :negation nil)))))))
+                     '((:line-checked 2 :line-assert 3 :column-checked 0 :face face1 :negation nil)
+                       (:line-checked 2 :line-assert 4 :column-checked 2 :face face2 :negation nil)
+                       (:line-checked 2 :line-assert 5 :column-checked 4 :face face3 :negation nil)))))))

 (ert-deftest test-parse-comments--non-assert-comment-single ()
   (let* ((str "
@@ -271,7 +321,7 @@ test-parse-comments--non-assert-comment-single
       (setq asserts (ert-font-lock--parse-comments))
       (should (eql (length asserts) 1))
       (should (equal (car asserts)
-                     '(:line-checked 2 :line-assert 3 :column-checked 4 :face "comment-face" :negation nil))))))
+                     '(:line-checked 2 :line-assert 3 :column-checked 4 :face comment-face :negation nil))))))

 (ert-deftest test-parse-comments--non-assert-comment-multiple ()
   (let* ((str "
@@ -288,9 +338,9 @@ test-parse-comments--non-assert-comment-multiple
       (setq asserts (ert-font-lock--parse-comments))
       (should (eql (length asserts) 3))
       (should (equal asserts
-                     '((:line-checked 2 :line-assert 3 :column-checked 4 :face "comment-face" :negation nil)
-                       (:line-checked 2 :line-assert 4 :column-checked 10 :face "comment-face" :negation nil)
-                       (:line-checked 2 :line-assert 5 :column-checked 18 :face "comment-face" :negation nil)))))))
+                     '((:line-checked 2 :line-assert 3 :column-checked 4 :face comment-face :negation nil)
+                       (:line-checked 2 :line-assert 4 :column-checked 10 :face comment-face :negation nil)
+                       (:line-checked 2 :line-assert 5 :column-checked 18 :face comment-face :negation nil)))))))

 (ert-deftest test-parse-comments--multiline-comment-single ()
@@ -308,7 +358,7 @@ test-parse-comments--multiline-comment-single
       (setq asserts (ert-font-lock--parse-comments))
       (should (eql (length asserts) 1))
       (should (equal (car asserts)
-                     '(:line-checked 3 :line-assert 4 :column-checked 3 :face "comment-face" :negation nil))))))
+                     '(:line-checked 3 :line-assert 4 :column-checked 3 :face comment-face :negation nil))))))

 (ert-deftest test-parse-comments--multiline-comment-multiple ()
   (let* ((str "
@@ -327,13 +377,47 @@ test-parse-comments--multiline-comment-multiple
       (setq asserts (ert-font-lock--parse-comments))
       (should (eql (length asserts) 2))
       (should (equal asserts
-                     '((:line-checked 3 :line-assert 4 :column-checked 3 :face "comment-face" :negation nil)
-                       (:line-checked 5 :line-assert 6 :column-checked 4 :face "comment-face" :negation nil)))))))
+                     '((:line-checked 3 :line-assert 4 :column-checked 3 :face comment-face :negation nil)
+                       (:line-checked 5 :line-assert 6 :column-checked 4 :face comment-face :negation nil)))))))

 ;;; Syntax highlighting assertion tests
 ;;

-(ert-deftest test-syntax-highlight-inline--caret-multiple-faces ()
+(ert-deftest test-syntax-highlight-inline--nil-list ()
+  (let ((str "
+var abc = function(d) {
+// ^ nil
+//   ^ !nil
+};
+
+"))
+    (with-temp-buffer
+      (insert str)
+      (javascript-mode)
+      (font-lock-ensure)
+
+      (ert-font-lock--check-faces
+       (ert-font-lock--parse-comments)))))
+
+(ert-deftest test-syntax-highlight-inline--face-list ()
+  (let ((str "
+var abc = function(d) {
+//   ^ (test-face-2 test-face-1 font-lock-variable-name-face)
+};
+
+"))
+    (with-temp-buffer
+      (insert str)
+      (javascript-mode)
+      (font-lock-ensure)
+
+      (add-face-text-property (point-min) (point-max) 'test-face-1)
+      (add-face-text-property (point-min) (point-max) 'test-face-2)
+
+      (ert-font-lock--check-faces
+       (ert-font-lock--parse-comments)))))
+
+(ert-deftest test-syntax-highlight-inline--caret-multiple-assertions ()
   (let ((str "
 var abc = function(d) {
 //   ^ font-lock-variable-name-face
@@ -364,6 +448,19 @@ test-syntax-highlight-inline--caret-wrong-face
       (should-error (ert-font-lock--check-faces
                      (ert-font-lock--parse-comments))))))

+(ert-deftest test-syntax-highlight-inline--caret-negated-wrong-face ()
+  (let* ((str "
+var abc = function(d) {
+//   ^ !not-a-face
+};
+"))
+    (with-temp-buffer
+      (insert str)
+      (javascript-mode)
+      (font-lock-ensure)
+
+      (ert-font-lock--check-faces
+       (ert-font-lock--parse-comments)))))

 (ert-deftest test-syntax-highlight-inline--comment-face ()
   (let* ((str "
@@ -455,6 +552,12 @@ test-macro-test--file
   javascript-mode
   "correct.js")

+(ert-font-lock-deftest-file test-macro-test--file-no-asserts
+    "Check failing on files without assertions"
+  :expected-result :failed
+  javascript-mode
+  "no-asserts.js")
+
 (ert-font-lock-deftest-file test-macro-test--file-failing
     "Test reading wrong assertions from a file"
   :expected-result :failed
--
2.34.1