Modern JavaScript ctags configuration

Veezus Kreist
Adorable
Published in
3 min readOct 12, 2017

The other day I was surfing around for a solution for this problem and came across Ray Grasso’s excellent post from April of 2015. I recommend reading the post for his solution; I’ve just modified it a bit. He also links to his dotfiles which include updates since the content of the post.

About ctags

Many years ago I became familiar with the basic ctags functionality in vim. But ctags itself is much more flexible and can be consumed by most editors. I highly encourage y’all to check out the ctags website, as well as plugins for popular editors such as vscode, atom, and sublime text.

Essentially, ctags will scan all files you ask it to and look for what it understands to be symbol definitions. These are classes, functions, variable names, and so on. ctags has a large list of known languages, but the JavaScript support hasn’t yet been the greatest.

To start, make sure you have exuberant-ctags installed, as opposed to regular ctags. You can brew install ctags on a macOS machine, for example, which will install the latest version of exuberant-ctags.

Inside vim

Generating ctags

I use ctags to jump to the definition of a symbol in a given project. There are generally two ways I achieve this: from a cursor over the top of a symbol and using normal-mode commands to search the list of defined tags.

Jumping to a definition under the cursor

While reading code and the cursor is on top of a known symbol, pressing Ctrl+] will jump to that definition.

Searching the available tags

If you know the tag you want but are nowhere near it, you can :tag <tagname> to jump to it’s definition. The :tag command supports tab completion; I often use :tag searches to jump to class or constant definitions.

Navigating through multiple matches

Either of these ways of jumping to definition may result in multiple tag matches. You can navigate the tag stack with :tnext (briefly, as :tn) or :tprevious (:tp). If you’d like to see a list of definitions found for any given tag, you can :tselect (:tsel).

Including other packages in your generated tags file

Although it can take a prohibitively long time based on the size of your dependency graph, I have found it useful in some cases to include my node_modules directory in my tags file. Whereas I usually just include basic directories like app, config, and lib, there are times when I’ll want to inspect the source of a module in the same session as my application’s code. One example would be when I’m using a library module for the first time and I’m unsure how things are working under the covers, or why what I expect to happen just isn’t.

Your new .ctags file

After a bit of pruning and studying, this is the JavaScript-specific portion of my ~/.ctags file looks like this:

--exclude=node_modules
--exclude=gulp
--languages=-javascript--langdef=js
--langmap=js:.js
--langmap=js:+.jsx
--regex-js=/[ \t.]([A-Z][A-Z0-9._$]+)[ \t]*[=:][ \t]*([0-9"'\[\{]|null)/\1/n,constant/--regex-js=/\.([A-Za-z0-9._$]+)[ \t]*=[ \t]*\{/\1/o,object/
--regex-js=/['"]*([A-Za-z0-9_$]+)['"]*[ \t]*:[ \t]*\{/\1/o,object/
--regex-js=/([A-Za-z0-9._$]+)\[["']([A-Za-z0-9_$]+)["']\][ \t]*=[ \t]*\{/\1\.\2/o,object/
--regex-js=/([A-Za-z0-9._$]+)[ \t]*=[ \t]*\(function\(\)/\1/c,class/
--regex-js=/['"]*([A-Za-z0-9_$]+)['"]*:[ \t]*\(function\(\)/\1/c,class/
--regex-js=/class[ \t]+([A-Za-z0-9._$]+)[ \t]*/\1/c,class/
--regex-js=/([A-Za-z$][A-Za-z0-9_$()]+)[ \t]*=[ \t]*[Rr]eact.createClass[ \t]*\(/\1/c,class/
--regex-js=/([A-Z][A-Za-z0-9_$]+)[ \t]*=[ \t]*[A-Za-z0-9_$]*[ \t]*[{(]/\1/c,class/
--regex-js=/([A-Z][A-Za-z0-9_$]+)[ \t]*:[ \t]*[A-Za-z0-9_$]*[ \t]*[{(]/\1/c,class/
--regex-js=/([A-Za-z$][A-Za-z0-9_$]+)[ \t]*=[ \t]*function[ \t]*\(/\1/f,function/--regex-js=/(function)*[ \t]*([A-Za-z$_][A-Za-z0-9_$]+)[ \t]*\([^)]*\)[ \t]*\{/\2/f,function/
--regex-js=/['"]*([A-Za-z$][A-Za-z0-9_$]+)['"]*:[ \t]*function[ \t]*\(/\1/m,method/
--regex-js=/([A-Za-z0-9_$]+)\[["']([A-Za-z0-9_$]+)["']\][ \t]*=[ \t]*function[ \t]*\(/\2/m,method/
--langdef=typescript
--langmap=typescript:.ts
--regex-typescript=/^[ \t]*(export)?[ \t]*class[ \t]+([a-zA-Z0-9_]+)/\2/c,classes/
--regex-typescript=/^[ \t]*(export)?[ \t]*module[ \t]+([a-zA-Z0-9_]+)/\2/n,modules/
--regex-typescript=/^[ \t]*(export)?[ \t]*function[ \t]+([a-zA-Z0-9_]+)/\2/f,functions/
--regex-typescript=/^[ \t]*export[ \t]+var[ \t]+([a-zA-Z0-9_]+)/\1/v,variables/
--regex-typescript=/^[ \t]*var[ \t]+([a-zA-Z0-9_]+)[ \t]*=[ \t]*function[ \t]*\(\)/\1/v,varlambdas/
--regex-typescript=/^[ \t]*(export)?[ \t]*(public|private)[ \t]+(static)?[ \t]*([a-zA-Z0-9_]+)/\4/m,members/
--regex-typescript=/^[ \t]*(export)?[ \t]*interface[ \t]+([a-zA-Z0-9_]+)/\2/i,interfaces/
--regex-typescript=/^[ \t]*(export)?[ \t]*enum[ \t]+([a-zA-Z0-9_]+)/\2/e,enums/
--regex-typescript=/^[ \t]*import[ \t]+([a-zA-Z0-9_]+)/\1/I,imports/

Wrapping it up into an alias

Finally, and following Mr. Grasso’s example, let’s get rid of some over-greedy matching and wrap it up into a nice alias:

alias jtags=”ctags -R app config lib && sed -i ‘’ -E ‘/^(if|switch|function|module\.exports|it|describe).+language:js$/d’ tags”

Happy vimming!

--

--