]> git.eshelyaron.com Git - emacs.git/commitdiff
Generate compatibility report for multiple Emacs versions
authorYuan Fu <casouri@gmail.com>
Mon, 30 Dec 2024 08:14:37 +0000 (00:14 -0800)
committerEshel Yaron <me@eshelyaron.com>
Sat, 4 Jan 2025 20:25:50 +0000 (21:25 +0100)
* admin/tree-sitter/compat-template.html: Update template.
* admin/tree-sitter/treesit-admin.el:
(treesit-admin--builtin-language-sources): Add sources.
(treesit-admin--builtin-modes): New variable.
(treesit-admin--verify-major-mode-queries): Don't need to pass
LANGS argument anymore.
(treesit-admin-verify-major-mode-queries): Move, and use
treesit-admin--builtin-modes.
(treesit-admin--mode-languages): Set some variables so it gets
all the languages.
(treesit-admin--find-latest-compatible-revision): Also return
commit timestamp.
(treesit-admin--generate-compatibility-report): New parameter
EMACS-EXECUTABLES.  Support generating report for multiple Emacs
versions.
* lisp/treesit.el (treesit--language-git-timestamp): New function.

(cherry picked from commit 0b1986ba5243b150bf6891f51827a4dae28447b0)

admin/tree-sitter/compat-template.html
admin/tree-sitter/treesit-admin.el
lisp/treesit.el

index f310d1b9ee52348d5ca8741edd725fb29070fa5f..23b5b55d7b32194ff820dba584e333e0fcfd89ff 100644 (file)
@@ -9,6 +9,7 @@
           width: min(90vw, 40rem);
           margin: auto;
           margin-top: 2rem;
+          margin-bottom: 2rem;
           font-family: ui-serif;
       }
       thead {
@@ -19,6 +20,8 @@
       }
       table td {
           padding: 0.5rem 1rem;
+          width: 10rem;
+          word-break: break-all;
       }
       .head {
           background: lightgreen;
     </style>
   </head>
   <body>
-    <h1>Tree-sitter grammar compatibility for Emacs ___REPLACE_EMACS_VERSION___</h1>
-    <p>This is an auto-generated report of the latest compatible versions of tree-sitter grammars for each major mode.  A <span class="head">green background</span> on the version indicates that the major mode is compatible with the latest commit in the upstream grammar repo.</p>
+    <h1>Emacs tree-sitter grammar compatibility</h1>
+    <p>This is an auto-generated report of the last compatible version for each grammar in each Emacs version.  A <span class="head">green background</span> on the version indicates that the Emacs version is compatible with the latest commit in the upstream grammar repo.</p>
     <p>This report is generated on ___REPLACE_TIME___.</p>
     <table>
-      <thead>
-        <tr>
-          <td>Major mode</td>
-          <td>Language</td>
-          <td>Latest compatible version</td>
-        </tr>
-      </thead>
-      <tbody>
 ___REPLACE_TABLE___
-      </tbody>
     </table>
   </body>
 </html>
index c2b95888652be46d4a2c74128cfe56c9dd9864b6..d0e2a4ffe4576c2f24d7aa8ceb03d622c20a3327 100644 (file)
     (cmake "https://github.com/uyha/tree-sitter-cmake")
     (dockerfile "https://github.com/camdencheek/tree-sitter-dockerfile")
     (go "https://github.com/tree-sitter/tree-sitter-go")
-    (ruby "https://github.com/tree-sitter/tree-sitter-ruby"))
+    (ruby "https://github.com/tree-sitter/tree-sitter-ruby")
+    (javascript "https://github.com/tree-sitter/tree-sitter-javascript")
+    (typescript "https://github.com/tree-sitter/tree-sitter-typescript"
+                nil "typescript/src")
+    (tsx "https://github.com/tree-sitter/tree-sitter-typescript"
+         nil "tsx/src")
+    (json "https://github.com/tree-sitter/tree-sitter-json")
+    (rust "https://github.com/tree-sitter/tree-sitter-rust")
+    (php "https://github.com/tree-sitter/tree-sitter-php"
+         nil "php/src")
+    (css "https://github.com/tree-sitter/tree-sitter-css")
+    (phpdoc "https://github.com/claytonrcarter/tree-sitter-phpdoc")
+    (doxygen "https://github.com/tree-sitter-grammars/tree-sitter-doxygen")
+    (lua "https://github.com/tree-sitter-grammars/tree-sitter-lua")
+    (python "https://github.com/tree-sitter/tree-sitter-python")
+    (html "https://github.com/tree-sitter/tree-sitter-html")
+    (elixir "https://github.com/elixir-lang/tree-sitter-elixir")
+    (heex "https://github.com/phoenixframework/tree-sitter-heex")
+    (java "https://github.com/tree-sitter/tree-sitter-java")
+    (jsdoc "https://github.com/tree-sitter/tree-sitter-jsdoc"))
   "A list of sources for the builtin modes.
 The source information are in the format of
 `treesit-language-source-alist'.  This is for development only.")
 
-(defun treesit-admin--verify-major-mode-queries (modes langs source-alist grammar-dir)
+(defvar treesit-admin--builtin-modes
+  '( c-ts-mode c++-ts-mode cmake-ts-mode dockerfile-ts-mode
+     go-ts-mode ruby-ts-mode js-ts-mode typescript-ts-mode tsx-ts-mode
+     json-ts-mode rust-ts-mode php-ts-mode css-ts-mode lua-ts-mode
+     html-ts-mode elixir-ts-mode heex-ts-mode java-ts-mode)
+  "Builtin tree-sitter modes that we check.")
+
+(defun treesit-admin--verify-major-mode-queries (modes source-alist grammar-dir)
   "Verify font-lock queries in MODES.
 
-LANGS is a list of languages, it should cover all the languages used by
-major modes in MODES.  SOURCE-ALIST should have the same shape as
+SOURCE-ALIST should have the same shape as
 `treesit-language-source-alist'.  GRAMMAR-DIR is a temporary direction
 in which grammars are installed.
 
@@ -101,7 +126,9 @@ queries that has problems with latest grammar."
         (invalid-feature-list nil)
         (valid-modes nil)
         (mode-language-alist nil)
-        (file-modes-alist nil))
+        (file-modes-alist nil)
+        (langs (cl-remove-duplicates
+                (mapcan #'treesit-admin--mode-languages modes))))
     (dolist (lang langs)
       (let* ((recipe (assoc lang source-alist))
              (ver (apply #'treesit--install-language-grammar-1
@@ -191,17 +218,6 @@ queries that has problems with latest grammar."
                           (nth 2 entry)))))
       (special-mode))))
 
-;;; Compatibility report
-
-(defvar treesit-admin-file-name (or load-file-name (buffer-file-name))
-  "Filename of the source file treesit-admin.el.")
-
-(defvar treesit-admin--compat-template-file-name
-  (expand-file-name "compat-template.html"
-                    (file-name-directory
-                     (or load-file-name (buffer-file-name))))
-  "Filename of the HTML template for the compatibility report.")
-
 (defun treesit-admin-verify-major-mode-queries ()
   "Varify font-lock queries in builtin major modes.
 
@@ -211,11 +227,21 @@ that version of grammar.  At the end of the process, show a list of
 queries that has problems with latest grammar."
   (interactive)
   (treesit-admin--verify-major-mode-queries
-   '(cmake-ts-mode dockerfile-ts-mode go-ts-mode ruby-ts-mode)
-   '(cmake dockerfile go ruby)
+   treesit-admin--builtin-modes
    treesit-admin--builtin-language-sources
    "/tmp/tree-sitter-grammars"))
 
+;;; Compatibility report
+
+(defvar treesit-admin-file-name (or load-file-name (buffer-file-name))
+  "Filename of the source file treesit-admin.el.")
+
+(defvar treesit-admin--compat-template-file-name
+  (expand-file-name "compat-template.html"
+                    (file-name-directory
+                     (or load-file-name (buffer-file-name))))
+  "Filename of the HTML template for the compatibility report.")
+
 (defun treesit-admin--validate-mode-lang (mode lang)
   "Validate queries for LANG in MODE.
 
@@ -244,7 +270,11 @@ Return non-nil if all queries are valid, nil otherwise."
   (let ((settings
          (with-temp-buffer
            (ignore-errors
-             (funcall mode)
+             ;; TODO: A more generic way to find all queries.
+             (let ((c-ts-mode-enable-doxygen t)
+                   (c-ts-mode-enable-doxygen t)
+                   (java-ts-mode-enabel-doxygen t))
+               (funcall mode))
              (font-lock-mode -1)
              treesit-font-lock-settings)))
         (all-queries-valid t))
@@ -266,10 +296,11 @@ instead.
 
 Return a plist of the form
 
-    (:version VERSION :head-version HEAD-VERSION).
+    (:version VERSION :head-version HEAD-VERSION :timstamp TIMESTAMP).
 
 HEAD-VERSION is the version of the HEAD, VERSION is the latest
-compatible version."
+compatible version.  TIMESTAMP is the commit date of VERSION in UNIX
+epoch format."
   (let ((treesit-extra-load-path (list grammar-dir))
         (treesit--install-language-grammar-full-clone t)
         (treesit--install-language-grammar-blobless t)
@@ -278,7 +309,9 @@ compatible version."
         (emacs-executable
          (or emacs-executable
              (expand-file-name invocation-name invocation-directory)))
-        head-version version exit-code)
+        head-version version exit-code timestamp)
+    (when (not recipe)
+      (signal 'treesit-error `("Cannot find recipe" ,language)))
     (pcase-let ((`(,url ,revision ,source-dir ,cc ,c++ ,commit)
                  recipe))
       (with-temp-buffer
@@ -294,6 +327,7 @@ compatible version."
             (treesit--build-grammar
              workdir grammar-dir language source-dir cc c++))
           (setq version (treesit--language-git-revision workdir))
+          (setq timestamp (treesit--language-git-timestamp workdir))
           (message "Validateing version %s" version)
           (setq exit-code
                 (call-process
@@ -307,7 +341,7 @@ compatible version."
                                    ',mode ',language)
                                   (kill-emacs 0)
                                 (kill-emacs -1)))))))))
-    (list :version version :head-version head-version)))
+    (list :version version :head-version head-version :timestamp timestamp)))
 
 (defun treesit-admin--last-compatible-grammar-for-modes
     (modes source-alist grammar-dir &optional emacs-executable)
@@ -336,47 +370,83 @@ VERSION and HEAD-VERSION in the plist are the same as in
    modes))
 
 (defun treesit-admin--generate-compatibility-report
-    (modes out-file &optional emacs-executable)
-  "Generate a language compatibility report for MODES.
-
-If EMACS-EXECUTABLE is non-nil, use it for validating queries.  Write
-the report to OUT-FILE."
-  (interactive)
-  (with-temp-buffer
-    (let ((table (treesit-admin--last-compatible-grammar-for-modes
-                  modes
-                  treesit-admin--builtin-language-sources
-                  "/tmp/treesit-grammar")))
-      (dolist (entry table)
-        (let ((mode (car entry)))
-          (dolist (entry (cdr entry))
-            (let* ((lang (car entry))
-                   (version (plist-get (cdr entry) :version))
-                   (head-version (plist-get (cdr entry) :head-version))
-                   (classname
-                    (if (equal version head-version) "head" "")))
-              (insert (format "<tr><td>%s</td><td>%s</td><td class=\"%s\">%s</td></tr>\n"
-                              mode lang classname version)))))))
-    (let ((time (current-time-string nil t))
-          (table-text (buffer-string))
-          (emacs-version
-           (if emacs-executable
-               (with-temp-buffer
-                 (call-process emacs-executable nil t nil
-                               "-Q" "--batch"
-                               "--eval" "(princ emacs-version)")
-                 (buffer-string))
-             emacs-version)))
-      (erase-buffer)
-      (insert-file-contents treesit-admin--compat-template-file-name)
-      (goto-char (point-min))
-      (search-forward "___REPLACE_EMACS_VERSION___")
-      (replace-match emacs-version t)
-      (search-forward "___REPLACE_TIME___")
-      (replace-match (format "%s UTC" time) t)
-      (search-forward "___REPLACE_TABLE___")
-      (replace-match table-text t)
-      (write-region (point-min) (point-max) out-file))))
+    (emacs-executables modes out-file)
+  "Generate a table for language compatibiity for MODES.
+
+Note that this only works for Emacs 31 and later, because before Emacs
+31 we can't validate a compiled query (because there's a bug preventing
+us from eager compiling a compiled query that's already lazily
+compiled).
+
+EMACS-EXECUTABLES is a list of Emacs executbles to check for."
+  (let ((tables
+         (mapcar
+          (lambda (emacs)
+            (cons (with-temp-buffer
+                    (call-process emacs nil t nil
+                                  "-Q" "--batch"
+                                  "--eval" "(princ emacs-version)")
+                    (buffer-string))
+                  (treesit-admin--last-compatible-grammar-for-modes
+                   modes
+                   treesit-admin--builtin-language-sources
+                   "/tmp/treesit-grammar"
+                   emacs)))
+          emacs-executables))
+        (database (make-hash-table :test #'equal))
+        languages)
+    (dolist (table tables)
+      (dolist (mode-entry (cdr table))
+        (dolist (language-entry (cdr mode-entry))
+          (let* ((lang (car language-entry))
+                 (plist (cdr language-entry))
+                 ;; KEY = (LANG . EMACS-VERSION)
+                 (key (cons lang (car table)))
+                 (existing-plist (gethash key database)))
+            (push lang languages)
+            ;; If there are two major modes that uses LANG, and they
+            ;; have different compatible versions, use the older
+            ;; version.
+            (when (or (not existing-plist)
+                      (< (plist-get plist :timestamp)
+                         (plist-get existing-plist :timestamp)))
+              (puthash key plist database))))))
+    (setq languages (cl-sort (cl-remove-duplicates languages)
+                             (lambda (a b)
+                               (string< (symbol-name a) (symbol-name b)))))
+    ;; Compose HTML table.
+    (with-temp-buffer
+      (insert "<tr><th>Language</th>")
+      (dolist (emacs-version (mapcar #'car tables))
+        (insert (format "<th>%s</th>" emacs-version)))
+      (insert "</tr>\n")
+      (dolist (lang languages)
+        (insert "<tr>")
+        (insert (format "<td>%s</td>" lang))
+        (dolist (emacs-version (mapcar #'car tables))
+          (let* ((key (cons lang emacs-version))
+                 (plist (gethash key database))
+                 (version (plist-get plist :version))
+                 (head-version (plist-get plist :head-version))
+                 (classname
+                  (if (equal version head-version) "head" "")))
+            (if (not plist)
+                (insert "<td></td>")
+              (insert (format "<td class=\"%s\">%s</td>"
+                              classname version)))))
+        (insert "</tr>\n"))
+
+      ;; Compose table with template and write to out file.
+      (let ((time (current-time-string nil t))
+            (table-text (buffer-string)))
+        (erase-buffer)
+        (insert-file-contents treesit-admin--compat-template-file-name)
+        (goto-char (point-min))
+        (search-forward "___REPLACE_TIME___")
+        (replace-match (format "%s UTC" time) t)
+        (search-forward "___REPLACE_TABLE___")
+        (replace-match table-text t)
+        (write-region (point-min) (point-max) out-file)))))
 
 (provide 'treesit-admin)
 
index 110d5d840678d7ba3690edae8e50674d14e76d09..3f79b9590a46f34004581ef86da89faabef88cb9 100644 (file)
@@ -4231,6 +4231,16 @@ nil."
       (string-trim (buffer-string)))
      (t nil))))
 
+(defun treesit--language-git-timestamp (repo-dir)
+  "Return the commit date in REPO-DIR in UNIX epoch.
+
+Return nil if failed to run command."
+  (with-temp-buffer
+    (if (eq 0 (call-process
+               "git" nil t nil "-C" repo-dir "log" "-1" "--format=%ct"))
+        (string-to-number (string-trim (buffer-string)))
+      nil)))
+
 (defun treesit--call-process-signal (&rest args)
   "Run `call-process' with ARGS.
 If it returns anything but 0, signal an error.  Use the buffer