Javascript code navigation in counsel-etags

  |   Source

Javascript code navigation is supported by counsel-etags out of box.

It supports new javascript syntax like arrow function because counsel-etags is only frontend.

It reads tags file created by backend CLI program Ctags. Ctags uses regular expression to extract tag name from source code.

But there are some syntax which regular expression could not help.

For example, json object path can't be extracted by regular expression.

Given an object a in file A,

var a = {
  b: {
    c: 3,

File B has code let v1 = a.b.c;, how can we jump to the definition of the field c from json path a.b.c?

The solution is to use Lisp to parse code in file A and generate extra navigation data which could be appended to tags file generated by Ctags.

The algorithm is simple,

  • Traverse all the field of object a in file A. Use API js2-print-json-path from js2-mode to get json path of current field.
  • The json path could be regarded as tags name. We've already got file name and line number. So there is enough information to create navigation data for tags file. Here is tags file format.

Necessary utilities are already provided by counsel-etags v1.8.7,

  • After tags files is generated by Ctags, the hook counsel-etags-after-update-tags-hook is executed. Users can append tags file in this hook
  • (counsel-etags-tag-line code-snippet tag-name line-number byte-offset) return a line which could be appended into tags file

My current project uses a technology called styled-components which has an advanced feature theming.

It could dynamically change web application's appearance and is a critical technology for our application to support multiple customer. Application's theme is basically a file containing a huge json object. So it's important that developers can jump to the corresponding json object's field by json path.




(require 'counsel-etags)

(defun my-manual-update-tag-file (code-file tags-file)
  (let* ((dir (file-name-directory tags-file))
         (path (concat dir code-file))
    (unless (featurep 'js2-mode) (require 'js2-mode))
      (insert-file-contents path)
      (goto-char (point-min))
      ;; find all js object property names
      (while (re-search-forward "\"?[a-z][a-zA-Z0-9]*\"?:" (point-max) t)
        (when (setq jp (js2-print-json-path))
          (setq curline (string-trim (buffer-substring-no-properties (line-beginning-position)
          (setq tagstr (concat tagstr
                               (counsel-etags-tag-line curline
                                                       (count-lines 1 (point))
        ;; move focus to next search
        (goto-char (line-end-position))))
    (when tagstr
        (insert-file-contents tags-file)
        (goto-char (line-end-position))
        (insert (format "\n\014\n%s,%d\n%s" code-file 0 tagstr))
        (write-region (point-min) (point-max) tags-file nil :silent)))))

(defun counsel-etags-after-update-tags-hook-setup (tags-file)
    (my-manual-update-tag-file "frontend/theming/themes/darkTheme.js" tags-file)
    (my-manual-update-tag-file "frontend/theming/themes/lightTheme.js" tags-file))
(add-hook 'counsel-etags-after-update-tags-hook 'counsel-etags-after-update-tags-hook-setup)
Comments powered by Disqus