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