branch: externals/matlab-mode
commit e86ad99c0b25cc22b57546003c6d7a4fbe824d27
Author: John Ciolfi <john.ciolfi...@gmail.com>
Commit: John Ciolfi <john.ciolfi...@gmail.com>

    matlab-ts-mode: added test for matlab-ts-view-parse-errors
---
 matlab-ts-mode.el                                  | 108 +++++++++++++++++++--
 tests/t-utils.el                                   | 107 +++++++++++++++++++-
 .../view_parse_errors_simple.m                     |   6 ++
 .../view_parse_errors_simple_expected.txt          |   5 +
 tests/test-matlab-ts-mode-view-parse-errors.el     |  82 ++++++++++++++++
 5 files changed, 300 insertions(+), 8 deletions(-)

diff --git a/matlab-ts-mode.el b/matlab-ts-mode.el
index 61152c1259..58e41b4eaf 100644
--- a/matlab-ts-mode.el
+++ b/matlab-ts-mode.el
@@ -35,6 +35,7 @@
 
 ;;; Code:
 
+(require 'compile)
 (require 'treesit)
 
 (require 'matlab--access)
@@ -3267,6 +3268,102 @@ Within comments, the following markers will be 
highlighted:
     (grep (concat grep-command "-wie \"" pattern "\" "
                   (file-name-nondirectory (buffer-file-name))))))
 
+;;; View parse errors
+
+(defun matlab-ts-mode--get-parse-errors ()
+  "Return a string of parse errors in matlab-ts-mode current buffer."
+
+  ;; See: tests/test-matlab-ts-mode-view-parse-errors.el
+
+  (let ((capture-errors (treesit-query-capture (treesit-buffer-root-node) 
'((ERROR) @e)))
+        (result-list '())
+        (buf-name (if (buffer-file-name)
+                      (file-name-nondirectory (buffer-file-name))
+                    (buffer-name))))
+    (dolist (capture-error capture-errors)
+      (let* ((error-node (cdr capture-error))
+             (start-point (treesit-node-start error-node))
+             (start-line (line-number-at-pos start-point))
+             (start-col (save-excursion ;; error messages are one based columns
+                          (goto-char start-point)
+                          (1+ (current-column))))
+             (end-point (treesit-node-end error-node))
+             (end-line (line-number-at-pos end-point))
+             (end-col (save-excursion
+                        (goto-char end-point)
+                        (1+ (current-column)))))
+        (push (format
+               "%s:%d:%d: error: parse error from line %d:%d to line %d:%d 
(point %d to %d)\n"
+               buf-name
+               start-line start-col
+               start-line start-col
+               end-line end-col
+               start-point
+               end-point)
+              result-list)))
+    (let ((errs (mapconcat #'identity (reverse result-list))))
+      (if (string= errs "")
+          (setq errs "No tree-sitter errors\n")
+        (setq errs (concat "Tree-sitter parse errors.\n" errs)))
+      errs)))
+
+(defvar-local matlab-ts-parse-errors-compilation--buffer nil)
+
+(defun matlab-ts-mode--view-parse-errors-recompile ()
+  "Get latest parse errors."
+  (interactive)
+  (unless matlab-ts-parse-errors-compilation--buffer
+    (error "No previously parsed buffer"))
+  (when (not (buffer-live-p matlab-ts-parse-errors-compilation--buffer))
+    (error "Previously parsed buffer was killed"))
+  (with-current-buffer matlab-ts-parse-errors-compilation--buffer
+    (matlab-ts-view-parse-errors 'no-pop-to-buffer)))
+
+(defvar-keymap matlab-ts-parse-errors-mode-map
+  "g" #'matlab-ts-mode--view-parse-errors-recompile)
+
+;; Note, we are using a slightly shorter names (no -mode)
+;;   matlab-ts-view-parse-errors
+;;   matlab-ts-parse-errors-mode
+;; so the mode line indicator, "matlab-ts-parse-errors" is slightly shorter
+
+(define-compilation-mode matlab-ts-parse-errors-mode "m-ts-parse-errors"
+  "The variant of `compilation-mode' used for `matlab-ts-view-parse-errors'."
+  (setq-local matlab-ts-parse-errors-compilation--current-buffer 
(current-buffer)))
+
+(defun matlab-ts-view-parse-errors (&optional no-pop-to-buffer)
+  "View parse errors in matlab-ts-mode current buffer.
+Optional NO-POP-TO-BUFFER, if non-nil will not run `pop-to-buffer'.
+The errors are displayed in a \"*parse errors in BUFFER-NAME*\" buffer
+and this buffer is returned."
+  (interactive)
+
+  (when (not (eq major-mode 'matlab-ts-mode))
+    (user-error "Current buffer major-mode, %s, is not matlab-ts-mode"
+                (symbol-name major-mode)))
+
+  (let ((m-buf (current-buffer))
+        (m-buf-dir default-directory)
+        parse-errors-buf
+        (errs (matlab-ts-mode--get-parse-errors))
+        (err-buf-name (concat "*parse errors in " (buffer-name) "*")))
+
+    (with-current-buffer (setq parse-errors-buf (get-buffer-create 
err-buf-name))
+      (read-only-mode -1)
+      (auto-revert-mode 0) ;; no need to save history
+      (erase-buffer)
+      (insert errs)
+      (matlab-ts-parse-errors-mode)
+      (goto-char (point-min))
+      (when (re-search-forward ": error: " nil t)
+        (beginning-of-line))
+      (setq default-directory m-buf-dir)
+      (setq-local matlab-ts-parse-errors-compilation--buffer m-buf)
+      (read-only-mode 1)
+      (when (not no-pop-to-buffer)
+        (pop-to-buffer (current-buffer) 'other-window)))
+    parse-errors-buf))
+
 ;;; Keymap
 
 (defvar-keymap matlab-ts-mode-map
@@ -3416,9 +3513,12 @@ mark at the beginning of the \"%% section\" and point at 
the end of the section"
      )
 
     "----"
-    ["View mlint code analyzer messages" (flycheck-list-errors)
+    ["View mlint code analyzer messages" flycheck-list-errors
      :help "View mlint code analyzer messages.
 Click FlyC in the mode-line for more options."]
+    ["View tree-sitter parse errors" matlab-ts-view-parse-errors
+     :help "The MATLAB tree-sitter is the engine behind matlab-ts-mode.
+Parse errors are not as detailed as mlint code analyzer messages."]
     "----"
     ["Jump to function" imenu]
     "----"
@@ -3566,10 +3666,6 @@ so configuration variables of that mode, do not affect 
this mode.
     ;; Activate MATLAB script ";; heading" matlab-sections-minor-mode if needed
     (matlab-sections-auto-enable-on-mfile-type-fcn 
(matlab-ts-mode--mfile-type))
 
-    ;; TODO view errors
-    ;;      Add matlab-ts-mode-view-syntax-errors in a compilation mode buffer 
(g to refresh),
-    ;;      add to menu
-    ;;
     ;; TODO [future] Indent - complex for statement
     ;;         function a = foo(inputArgument1)
     ;;             for (idx = (a.b.getStartValue(((inputArgument1 + 
someOtherFunction(b)) * 2 - ...
@@ -3699,4 +3795,4 @@ matlab-language-server-lsp-mode.org\n"
 ;; LocalWords:  funcall mfile elec foo'bar mapcar lsp noerror alnum featurep 
grep'ing mapconcat wie
 ;; LocalWords:  Keymap keymap netshell gud ebstop mlgud ebclear ebstatus mlg 
mlgud's subjob reindent
 ;; LocalWords:  DWIM dwim parens caar cdar utils fooenum mcode CRLF cmddual 
lang nconc listify kbd
-;; LocalWords:  matlabls vscode
+;; LocalWords:  matlabls vscode buf dolist
diff --git a/tests/t-utils.el b/tests/t-utils.el
index c44bfffee8..5e4d69688a 100644
--- a/tests/t-utils.el
+++ b/tests/t-utils.el
@@ -844,6 +844,109 @@ TODO add example test setup, see t-utils-test-font-lock."
     (setq error-msgs (reverse error-msgs))
     (should (equal error-msgs '()))))
 
+(defun t-utils-test-action (test-name lang-files action-fun)
+  "Run and record ACTION-FUN on each NAME.LANG file in LANG-FILES list.
+ACTION-FUN is a function that takes no arguments and is called
+in context of a temporary buffer containing NAME.LANG file from LANG-FILES.
+ACTION-FUN must return a string that is recorded into NAME_expected.txt.
+Within ACTION-FUN, `t-utils--buf-file' contains NAME.LANG.
+TEST-NAME is used in messages.
+
+The result of ACTION-FUN is compared against NAME_expected.txt.  If
+my_test_expected.txt does not exist or result does not match the existing
+my_test_expected.txt, my_test_expected.txt~ is generated and if it looks
+correct, you should rename it to my_test_expected.txt.
+
+Example test setup:
+
+  ./LANGUAGE-ts-mode.el
+  ./tests/test-LANGUAGE-ts-mode-view-parse-errors.el
+  ./tests/test-LANGUAGE-ts-mode-view-parse-errors-files/NAME1.LANG
+  ./tests/test-LANGUAGE-ts-mode-view-parse-errors-files/NAME1_expected.txt
+  ./tests/test-LANGUAGE-ts-mode-view-parse-errors-files/NAME2.LANG
+  ./tests/test-LANGUAGE-ts-mode-view-parse-errors-files/NAME2_expected.txt
+  ....
+
+Where ./tests/test-LANGUAGE-ts-mode-view-parse-errors.el exercises
+function LANGUAGE-ts-mode-view-parse-errors which shows a buffer
+containing the LANGUAGE tree-sitter parse errors.
+
+  (require \\='t-utils)
+  (require \\='LANGUAGE-ts-mode)
+
+  (defvar test-LANGUAGE-ts-mode-view-parse-errors--file nil)
+  
+  (defun test-LANGUAGE-ts-mode-view-parse-errors--file (lang-file)
+    \"Test an individual LANG-FILE.\"
+    (let ((test-LANGUAGE-ts-mode-view-parse-errors--file lang-file))
+      (ert-run-tests-interactively 
\"test-LANGUAGE-ts-mode-view-parse-errors\")))
+  
+  (defun test-LANGUAGE-ts-mode-view-parse-errors-action-fun ()
+    \"Exercise LANGUAGE-ts-mode-view-parse-errors on the current buffer.\"
+    (let* ((lang-file (file-name-nondirectory t-utils--buf-file))
+           (parse-errors-buf (LANGUAGE-ts-view-parse-errors))
+           (buf-name (buffer-name))
+           (contents (with-current-buffer parse-errors-buf
+                       (buffer-substring-no-properties (point-min) 
(point-max))))
+           (result (replace-regexp-in-string (rx bol (literal buf-name) \":\")
+                                             (concat lang-file \":\")
+                                             contents)))
+      (kill-buffer parse-errors-buf)
+      result))
+  
+  (ert-deftest test-LANGUAGE-ts-mode-view-parse-errors ()
+    \"Test LANGUAGE-ts-mode-view-parse-errors.
+  Using ./test-LANGUAGE-ts-mode-treesit-defun-name-files/NAME.m, compare
+  LANGUAGE-ts-mode-view-parse-error against
+  ./test-LANGUAGE-ts-mode-treesit-defun-name-files/NAME_expected.txt.  This 
loops
+  on all ./test-LANGUAGE-ts-mode-treesit-defun-name-files/NAME.m files.
+  
+  To add a test, create
+    ./test-LANGUAGE-ts-mode-treesit-defun-name-files/NAME.m
+  and run this function.  The baseline is saved for you as
+    ./test-LANGUAGE-ts-mode-treesit-defun-name-files/NAME_expected.txt~
+  after validating it, rename it to
+    ./test-LANGUAGE-ts-mode-treesit-defun-name-files/NAME_expected.txt\"
+  
+    (let* ((test-name \"test-LANGUAGE-ts-mode-view-parse-errors\")
+           (lang-files (t-utils-get-files
+                     test-name
+                     (rx \".m\" eos)
+                     nil
+                     test-LANGUAGE-ts-mode-view-parse-errors--file)))
+      (t-utils-error-if-no-treesit-for \\='LANGUAGE test-name)
+      (t-utils-test-action test-name lang-files
+       #\\='test-LANGUAGE-ts-mode-view-parse-errors-action-fun)))"
+
+  (let ((error-msgs '()))
+    (dolist (lang-file lang-files)
+      (with-temp-buffer
+
+        (let ((start-time (current-time)))
+
+          (message "START: %s %s" test-name lang-file)
+
+          (t-utils--insert-file-for-test lang-file)
+
+          (let* ((expected-file (replace-regexp-in-string "\\.[^.]+\\'" 
"_expected.txt" lang-file))
+                 (expected (when (file-exists-p expected-file)
+                             (with-temp-buffer
+                               (insert-file-contents-literally expected-file)
+                               (buffer-string))))
+                 (got (concat (symbol-name action-fun) " result:\n---\n"
+                              (funcall action-fun)))
+                 (got-file (concat expected-file "~")))
+
+            (kill-buffer)
+            (let ((error-msg (t-utils--baseline-check
+                              test-name start-time
+                              lang-file got got-file expected expected-file)))
+              (when error-msg
+                (push error-msg error-msgs)))))))
+    ;; Validate t-utils-test-outline-search-function result
+    (setq error-msgs (reverse error-msgs))
+    (should (equal error-msgs '()))))
+
 (defun t-utils--test-font-lock-checker (lang-file
                                         got got-file expected expected-file 
code-to-face)
   "Get error that includes the position of the first font face difference.
@@ -1884,7 +1987,7 @@ To debug a specific file-encoding test file
               (when error-msg
                 (push error-msg error-msgs)))))))
 
-    ;; Validate t-utils-test-file-encoding result
+    ;; Validate result
     (setq error-msgs (reverse error-msgs))
     (should (equal error-msgs '()))))
 
@@ -2284,7 +2387,7 @@ To debug a specific -parser test file
               (when error-msg
                 (push error-msg error-msgs)))))))
 
-    ;; Validate t-utils-test-file-encoding result
+    ;; Validate result
     (setq error-msgs (reverse error-msgs))
     (should (equal error-msgs '()))))
 
diff --git 
a/tests/test-matlab-ts-mode-view-parse-errors-files/view_parse_errors_simple.m 
b/tests/test-matlab-ts-mode-view-parse-errors-files/view_parse_errors_simple.m
new file mode 100644
index 0000000000..2815d3c26a
--- /dev/null
+++ 
b/tests/test-matlab-ts-mode-view-parse-errors-files/view_parse_errors_simple.m
@@ -0,0 +1,6 @@
+% -*- matlab-ts -*-
+
+a = = 2000;
+
+b = *= 2;
+
diff --git 
a/tests/test-matlab-ts-mode-view-parse-errors-files/view_parse_errors_simple_expected.txt
 
b/tests/test-matlab-ts-mode-view-parse-errors-files/view_parse_errors_simple_expected.txt
new file mode 100644
index 0000000000..d7a5daba87
--- /dev/null
+++ 
b/tests/test-matlab-ts-mode-view-parse-errors-files/view_parse_errors_simple_expected.txt
@@ -0,0 +1,5 @@
+test-matlab-ts-mode-view-parse-errors-action-fun result:
+---
+Tree-sitter parse errors.
+view_parse_errors_simple.m:3:3: error: parse error from line 3:3 to line 3:4 
(point 24 to 25)
+view_parse_errors_simple.m:5:3: error: parse error from line 5:3 to line 5:6 
(point 37 to 40)
diff --git a/tests/test-matlab-ts-mode-view-parse-errors.el 
b/tests/test-matlab-ts-mode-view-parse-errors.el
new file mode 100644
index 0000000000..45a3eb77e2
--- /dev/null
+++ b/tests/test-matlab-ts-mode-view-parse-errors.el
@@ -0,0 +1,82 @@
+;;; test-matlab-ts-mode-view-parse-errors.el --- -*- lexical-binding: t -*-
+;;
+;; Copyright 2025 Free Software Foundation, Inc.
+;;
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;;
+
+;;; Commentary:
+;;
+;; Test matlab-ts-mode-view-parse-errors
+;;
+
+;;; Code:
+
+(require 't-utils)
+(require 'matlab-ts-mode)
+
+(defvar test-matlab-ts-mode-view-parse-errors--file nil)
+
+(defun test-matlab-ts-mode-view-parse-errors--file (m-file)
+  "Test an individual M-FILE.
+This is provided for debugging.
+  M-: (test-matlab-ts-mode-view-parse-errors--file
+      \"test-matlab-ts-mode-view-parse-errors-files/M-FILE\")"
+  (let ((test-matlab-ts-mode-view-parse-errors--file m-file))
+    (ert-run-tests-interactively "test-matlab-ts-mode-view-parse-errors")))
+
+(defun test-matlab-ts-mode-view-parse-errors-action-fun ()
+  "Exercise `matlab-ts-mode-view-parse-errors' on the current buffer.
+Returns string result which is the contents of the
+  \"*parse errors for NAME*\"
+buffer created by `matlab-ts-mode-view-parse-errors'."
+  (let* ((m-file (file-name-nondirectory t-utils--buf-file))
+         (parse-errors-buf (matlab-ts-view-parse-errors))
+         (buf-name (buffer-name))
+         (contents (with-current-buffer parse-errors-buf
+                     (buffer-substring-no-properties (point-min) (point-max))))
+         (result (replace-regexp-in-string (rx bol (literal buf-name) ":")
+                                           (concat m-file ":")
+                                           contents)))
+    (kill-buffer parse-errors-buf)
+    result))
+
+(ert-deftest test-matlab-ts-mode-view-parse-errors ()
+  "Test `matlab-ts-mode-view-parse-errors'.
+Using ./test-matlab-ts-mode-treesit-defun-name-files/NAME.m, compare
+`matlab-ts-mode-view-parse-errors' against
+./test-matlab-ts-mode-treesit-defun-name-files/NAME_expected.txt.  This loops
+on all ./test-matlab-ts-mode-treesit-defun-name-files/NAME.m files.
+
+To add a test, create
+  ./test-matlab-ts-mode-treesit-defun-name-files/NAME.m
+and run this function.  The baseline is saved for you as
+  ./test-matlab-ts-mode-treesit-defun-name-files/NAME_expected.txt~
+after validating it, rename it to
+  ./test-matlab-ts-mode-treesit-defun-name-files/NAME_expected.txt"
+
+  (let* ((test-name "test-matlab-ts-mode-view-parse-errors")
+         (m-files (t-utils-get-files
+                   test-name
+                   (rx ".m" eos)
+                   nil
+                   test-matlab-ts-mode-view-parse-errors--file)))
+    (t-utils-error-if-no-treesit-for 'matlab test-name)
+    (t-utils-test-action test-name m-files 
#'test-matlab-ts-mode-view-parse-errors-action-fun)))
+
+(provide 'test-matlab-ts-mode-view-parse-errors)
+;;; test-matlab-ts-mode-view-parse-errors.el ends here
+
+;; LocalWords:  utils defun nondirectory buf bol treesit eos

Reply via email to