Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

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.

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:

BASH
ctags -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:

JAVASCRIPT
const Comp1 = () => { const x = (arg) => console.log(arg) x(4) return(<Comp2 text={<p>Some Text</p>}/>) } const Comp2 = (props) => { return ( props.text ) }

After running:

BASH
ctags -e --languages=javascript --exclude=node_modules --exclude=build --quiet test.js

The TAGS file shows only:

PLAINTEXT
test.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:

JAVASCRIPT
const 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:

PLAINTEXT
test.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.

1

Comments 0

Answers 2

Views 45

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

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:

1. Preprocess or Strip JSX When Generating Tags

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.

2. Customize ctags with Regex Options

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.

3. Consider Using an Alternative Tag Generator

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

0

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:

EMACS
M-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:

BASH
npm 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:

BASH
which typescript-language-server

In Emacs check if this path is already present by checking the value of exec-path with:

EMACS
M-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.

Discussion

No comments yet.