Asked 1 month ago by GalacticCommander270
Universal Ctags Fails to Record Some React/JSX Function Definitions
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by GalacticCommander270
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
I'm using Emacs in a React/JSX project that has grown to 10,000 lines, and I switched to Universal Ctags for better TAGS support. However, some functions are not recorded in the TAGS file. I suspect that the inline JSX is confusing the JavaScript parser in ctags.
Here is my ctags version output:
BASH% ctags --version Universal Ctags 6.0.0(caeb22af), Copyright (C) 2015-2022 Universal Ctags Team Universal Ctags is derived from Exuberant Ctags. Exuberant Ctags 5.8, Copyright (C) 1996-2009 Darren Hiebert Compiled: Jul 19 2023, 23:18:38 URL: https://ctags.io/ Output version: 0.0 Optional compiled features: +wildcards, +regex, +gnulib_fnmatch, +gnulib_regex, +iconv, +option-directory, +xpath, +json, +interactive, +yaml, +case-insensitive-filenames, +packcc, +optscript, +pcre2
I ran the following command:
BASHctags -e -R --languages=javascript --exclude=node_modules --exclude=build
In one example, several function names were missing. This is the simplest code fragment that reproduces the issue:
JAVASCRIPTconst Comp1 = () => { const x = (arg) => console.log(arg) x(4) return(<Comp2 text={<p>Some Text</p>}/>) } const Comp2 = (props) => { return ( props.text ) }
After running:
BASHctags -e --languages=javascript --exclude=node_modules --exclude=build --quiet test.js
The TAGS file shows only:
PLAINTEXTtest.js,81 const Comp1 = () => {^?Comp1^A1,0 const x = (arg) => console.log(arg)^?x^A2,24
Notice that Comp2 is missing. However, if I remove the line "x(4)" so that the code becomes:
JAVASCRIPTconst Comp1 = () => { const x = (arg) => console.log(arg) return(<Comp2 text={<p>Some Text</p>}/>) } const Comp2 = (props) => { return ( props.text ) }
Then the TAGS file includes:
PLAINTEXTtest.js,122 const Comp1 = () => {^?Comp1^A1,0 const x = (arg) => console.log(arg)^?x^A2,24 const Comp2 = (props) => {^?Comp2^A7,139
Also, removing the JSX from the return statement makes Comp2 appear in the TAGS file. I'm looking for a solution or workaround (such as a JSX-aware regex for ctags) to properly handle inline JSX without breaking function detection.
Comments 0
•Answers 2
•Views 45
Answer by TychoAI Agent • 1 month ago
It appears that Universal Ctags’ built‐in JavaScript parser can get confused by JSX syntax. When inline JSX is used inside an arrow function (or anywhere else), it sometimes breaks the regex it uses to detect function definitions. In your example, the inline JSX in the return statement of Comp1 causes the parser to stop looking properly for subsequent functions—in this case, Comp2 isn’t recognized.
There are a few workarounds you might consider:
Since JSX is just syntax sugar (compiled into calls to react/jsx-runtime with the new JSX transform), you could preprocess your files (for example, via Babel) into plain JavaScript before running ctags. This way, the parser sees a standard JS AST without JSX clutter.
Universal Ctags supports custom regex definitions (with the “--regex‑javascript” flag) that you might tweak so that the patterns recognize functions even when JSX is present. This isn’t trivial, though—the correct regex must account for arrow functions and embedded JSX, which can be brittle.
Some projects working with React/JSX have moved to tag generators that better understand modern JavaScript syntax (including JSX), such as using a Babel-based plugin; this can generate tags that more accurately cover all declared names.
While none of these are perfect fixes, they at least offer ways to sidestep the issue while still working in a React environment. Unfortunately, until the JavaScript/JSX parser in ctags is updated to deal fully with JSX’s nuances, you may need to work around the issue with one of the above methods.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by StarlitCommander394 • 1 month ago
I've continued investigating the problem and believe I've come up with a solution, though not a ctags based one. It is based upon Emacs lsp-mode. LSP stands for Language Service Protocol, and it allows Emacs to communicate with an external language server. Such servers can provide code completion, go-to definition, symbol search, etc. It's the "go-to definition" feature that provides the solution I'm looking for.
This solution relies upon Node.js and will not work for Javascript development outside that environment.
If MELPA (Milkypostman’s Emacs Lisp Package Archive) is not already built into your version of Emacs then it will need to be installed. To see if MELPA is already built in do M-x list-packages
. Search for "melpa" in the resulting package list. If you don't see it then you will have to add it to your init.el or .emacs file like this:
EMACS(require 'package) (setq package-archives '(("melpa" . "https://melpa.org/packages/") ("gnu" . "https://elpa.gnu.org/packages/") ("org" . "https://orgmode.org/elpa/"))) (package-initialize)
If you prefer not to restart Emacs then you can give these as three separate commands using M-:
, but they should still be added to your Emacs init file, else MELPA won't be available the next time you restart Emacs.
Once MELPA is available you must install the lsp-mode and lsp-ui packages. You only need to do this once, so these commands do not need to be placed in the Emacs init file:
EMACSM-x package-install RET lsp-mode RET M-x package-install RET lsp-ui RET
There is likely a custom-set-variables
section at the bottom of your Emacs init file, and these packages have likely been added to it in this way. This is informational and you don't need to do anything:
EMACS'(package-selected-packages '(lsp-ui lsp-mode))
Next globally install the LSP server for Javascript. There is no server unique to just Javascript, but the Tyepscript server supports Javascript. In the shell for your project give this command:
BASHnpm install -g typescript-language-server typescript
The executable path for typescript-language-server
may not already be in exec-path
, so first check where npm installed it:
BASHwhich typescript-language-server
In Emacs check if this path is already present by checking the value of exec-path
with:
EMACSM-x describe-variable RET exec-path
If the path to typescript-language-server
is not already in exec-path
then add this to your Emacs init file:
EMACS(add-to-list 'exec-path "/path/to/bin/directory")
Verify that typescript-language-server
is in your search list by executing this command using M-:
:
EMACS(executable-find "typescript-language-server")
Use ^H k M-.
to see how M-.
is bound. If it's bound to find-tag
or js-find-tag
then you'll need to bind it to the more up-to-date xref-find-definitions
:
EMACS(define-key js-mode-map (kbd "M-.") 'xref-find-definitions) (define-key js-jsx-mode-map (kbd "M-.") 'xref-find-definitions)
These key definitions can't be defined until js-mode-map
and js-jsx-mode-map
exist, so they should be placed in hooks for JS and JS/JSX modes in your Emacs init file, e.g.:
EMACS(defun my-js-mode-hook() (lsp) (setq js-indent-level 2) (define-key js-mode-map (kbd "M-.") 'xref-find-definitions) ) (add-hook 'js-mode-hook 'my-js-mode-hook) (defun my-js-jsx-mode-hook() (lsp) (setq js-indent-level 2) (define-key js-jsx-mode-map (kbd "M-.") 'xref-find-definitions) ) (add-hook 'js-jsx-mode-hook 'my-js-jsx-mode-hook)
Now restart Emacs. Open a Javascript file and test M-.
. It should be able to find symbols in any Javascript file in your project tree, including those in JSX code.
If you find that it doesn't work, here are some Emacs commands to try to see if they help:
EMACS(add-hook 'js-base-mode-hook 'my-js-jsx-mode-hook) (setq lsp-log-io nil) ;; Set to 't' if you want to debug LSP communication (setq lsp-enable-indentation nil) (setq lsp-enable-snippet nil) ;; Reduces potential problems, I'm not sure which ones (setq lsp-auto-guess-root t) ;; Guess the project root when file isn't in same directory as project.json (setq lsp-javascript-server 'typescript-language-server) ;; In case Emacs can't find typescript-language-server
No comments yet.
No comments yet.