You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

283 lines
16 KiB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>How to find all unused functions in JS buffers</title>
<meta name="description" content="The real power of Emacs lies in its extensibility. To be able to quickly hack some Elisp together to fix a specific problem right in your development environ...">
<link rel="shortcut icon" type="image/png" href="/favicon.png"/>
<link href="https://fonts.googleapis.com/css?family=Fira+Mono|Gentium+Book+Basic|Lato" rel="stylesheet">
<link rel="stylesheet" href="/assets/main.css">
<link rel="stylesheet" href="/css/cafe.css">
<link rel="canonical" href="https://emacs.cafe/emacs/javascript/2017/05/12/finding-unused-functions.html">
<link rel="alternate" type="application/rss+xml" title="Emacs café" href="/feed.xml">
</head>
<body>
<header class="site-header" role="banner">
<div class="wrapper">
<a class="site-title" href="/"><img src="/img/emacscafe.png"/>Emacs café</a>
<nav class="site-nav">
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
<label for="nav-trigger">
<span class="menu-icon">
<svg viewBox="0 0 18 15" width="18px" height="15px">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</span>
</label>
<div class="trigger">
<a class="page-link" href="/about/">About Emacs café</a>
</div>
</nav>
</div>
</header>
<main class="page-content" aria-label="Content">
<div class="wrapper">
<article class="post" itemscope itemtype="http://schema.org/BlogPosting">
<header class="post-header">
<h1 class="post-title" itemprop="name headline">How to find all unused functions in JS buffers</h1>
<p class="post-meta">
<time datetime="2017-05-12T14:43:00+02:00" itemprop="datePublished">
May 12, 2017
</time>
<span itemprop="author" itemscope itemtype="http://schema.org/Person"><span itemprop="name">Nicolas Petton</span></span>
</p>
</header>
<div class="post-content" itemprop="articleBody">
<p>The real power of Emacs lies in its extensibility. To be able to quickly hack
some Elisp together to fix a specific problem right in your development
environment is something quite unique to Emacs, and it makes it stand apart from
other text editors.</p>
<p>I’m working on a fairly large JavaScript code base for which maintenance can
sometimes be an issue.</p>
<p>Yesterday I wanted to quickly find all function definitions in a JavaScript file
that were not referenced anymore in the project, so I decided to hack some
Elisp to do that.</p>
<h2 id="what-do-we-already-have">What do we already have?</h2>
<p>Let’s see what building blocks are already available.</p>
<p><a href="https://github.com/nicolaspetton/xref-js2">xref-js2</a> makes it easy to find all
references to a specific function within a project,
and <a href="https://github.com/mooz/js2-mode">js2-mode</a> exposes an AST that can be
visited.</p>
<p>All in all, what I want to achieve shouldn’t be too hard to implement!</p>
<h2 id="first-steps">First steps</h2>
<p>I’m calling my small package <code class="highlighter-rouge">js2-unused</code>, so all functions and variables will
have that prefix.</p>
<p>We’ll need some packages along the way, so let’s require them:</p>
<figure class="highlight"><pre><code class="language-elisp" data-lang="elisp"><span class="p">(</span><span class="nb">require</span> <span class="ss">'seq</span><span class="p">)</span>
<span class="p">(</span><span class="nb">require</span> <span class="ss">'xref-js2</span><span class="p">)</span>
<span class="p">(</span><span class="nb">require</span> <span class="ss">'subr-x</span><span class="p">)</span></code></pre></figure>
<p>The first step is to find all function definitions within the current buffer.
<code class="highlighter-rouge">JS2-mode</code> has a function <code class="highlighter-rouge">js2-visit-ast</code> that makes it really easy to traverse
the entire AST tree.</p>
<p>We can first define a variable that will hold all function definition names that
we find:</p>
<figure class="highlight"><pre><code class="language-elisp" data-lang="elisp"><span class="p">(</span><span class="nb">defvar</span> <span class="nv">js2-unused-definitions</span> <span class="no">nil</span><span class="p">)</span></code></pre></figure>
<p>Now let’s traverse the AST and find all function definitions. We want to find:</p>
<ul>
<li>all assignments that assign to a function;</li>
<li>all function declarations that are named (skipping anonymous functions).</li>
</ul>
<figure class="highlight"><pre><code class="language-elisp" data-lang="elisp"><span class="p">(</span><span class="nb">defun</span> <span class="nv">js2-unused--find-definitions</span> <span class="p">()</span>
<span class="c1">;; Reset the value before visiting the AST</span>
<span class="p">(</span><span class="k">setq</span> <span class="nv">js2-unused-definitions</span> <span class="no">nil</span><span class="p">)</span>
<span class="p">(</span><span class="nv">js2-visit-ast</span> <span class="nv">js2-mode-ast</span>
<span class="nf">#'</span><span class="nv">js2-unused-visitor</span><span class="p">))</span>
<span class="p">(</span><span class="nb">defun</span> <span class="nv">js2-unused-visitor</span> <span class="p">(</span><span class="nv">node</span> <span class="nv">end-p</span><span class="p">)</span>
<span class="s">"Add NODE's name to `js2-unused-definitions` if it is a function."</span>
<span class="p">(</span><span class="nb">unless</span> <span class="nv">end-p</span>
<span class="p">(</span><span class="nb">cond</span>
<span class="c1">;; assignment to a function</span>
<span class="p">((</span><span class="nb">and</span> <span class="p">(</span><span class="nv">js2-assign-node-p</span> <span class="nv">node</span><span class="p">)</span>
<span class="p">(</span><span class="nv">js2-function-node-p</span> <span class="p">(</span><span class="nv">js2-assign-node-right</span> <span class="nv">node</span><span class="p">)))</span>
<span class="p">(</span><span class="nb">push</span> <span class="p">(</span><span class="nv">js2-node-string</span> <span class="p">(</span><span class="nv">js2-assign-node-left</span> <span class="nv">node</span><span class="p">))</span> <span class="nv">js2-unused-definitions</span><span class="p">))</span>
<span class="c1">;; function declaration (skipping anonymous ones)</span>
<span class="p">((</span><span class="nv">js2-function-node-p</span> <span class="nv">node</span><span class="p">)</span>
<span class="p">(</span><span class="nv">if-let</span> <span class="p">((</span><span class="nv">name</span> <span class="p">(</span><span class="nv">js2-function-name</span> <span class="nv">node</span><span class="p">)))</span>
<span class="p">(</span><span class="nb">push</span> <span class="nv">name</span> <span class="nv">js2-unused-definitions</span><span class="p">))))</span>
<span class="no">t</span><span class="p">))</span></code></pre></figure>
<h2 id="finding-references-using-xref-js2">Finding references using xref-js2</h2>
<p>Now that we can find and store all function names in a list, let’s use
<code class="highlighter-rouge">xref-js2</code> to filter the ones that are never referenced. If we find
unreferenced functions, we simply display a message listing them.</p>
<figure class="highlight"><pre><code class="language-elisp" data-lang="elisp"><span class="p">(</span><span class="nb">defun</span> <span class="nv">js2-unused-functions</span> <span class="p">()</span>
<span class="p">(</span><span class="nv">interactive</span><span class="p">)</span>
<span class="c1">;; Make sure that JS2 has finished parsing the buffer</span>
<span class="p">(</span><span class="nv">js2-mode-wait-for-parse</span>
<span class="p">(</span><span class="k">lambda</span> <span class="p">()</span>
<span class="c1">;; Walk the AST tree to find all function definitions</span>
<span class="p">(</span><span class="nv">js2-unused--find-definitions</span><span class="p">)</span>
<span class="c1">;; Use xref-js2 to filter the ones that are not referenced anywhere</span>
<span class="p">(</span><span class="k">let</span> <span class="p">((</span><span class="nv">unused</span> <span class="p">(</span><span class="nv">seq-filter</span> <span class="p">(</span><span class="k">lambda</span> <span class="p">(</span><span class="nv">name</span><span class="p">)</span>
<span class="p">(</span><span class="nb">null</span> <span class="p">(</span><span class="nv">xref-js2--find-references</span>
<span class="p">(</span><span class="nv">js2-unused--unqualified-name</span> <span class="nv">name</span><span class="p">))))</span>
<span class="nv">js2-unused-definitions</span><span class="p">)))</span>
<span class="c1">;; If there are unreferenced function, display a message</span>
<span class="p">(</span><span class="nb">apply</span> <span class="nf">#'</span><span class="nv">message</span> <span class="p">(</span><span class="k">if</span> <span class="nv">unused</span>
<span class="o">`</span><span class="p">(</span><span class="s">"Unused functions in %s: %s "</span>
<span class="o">,</span><span class="p">(</span><span class="nv">file-name-nondirectory</span> <span class="nv">buffer-file-name</span><span class="p">)</span>
<span class="o">,</span><span class="p">(</span><span class="nv">mapconcat</span> <span class="nf">#'</span><span class="nb">identity</span> <span class="nv">unused</span> <span class="s">" "</span><span class="p">))</span>
<span class="o">'</span><span class="p">(</span><span class="s">"No unused function found"</span><span class="p">)))))))</span>
<span class="p">(</span><span class="nb">defun</span> <span class="nv">js2-unused--unqualified-name</span> <span class="p">(</span><span class="nv">name</span><span class="p">)</span>
<span class="s">"Return the local name of NAME.
foo.bar.baz =&gt; baz"</span>
<span class="p">(</span><span class="nv">save-match-data</span>
<span class="p">(</span><span class="k">if</span> <span class="p">(</span><span class="nv">string-match</span> <span class="s">"\\.\\([^.]+\\)$"</span> <span class="nv">name</span><span class="p">)</span>
<span class="p">(</span><span class="nv">match-string</span> <span class="mi">1</span> <span class="nv">name</span><span class="p">)</span>
<span class="nv">name</span><span class="p">)))</span></code></pre></figure>
<h2 id="conclusion">Conclusion</h2>
<p>That’s it! In ~30 lines we can now find unreferenced functions in any JS file.
Sure, the code is not perfect, far from it, but it was hacked together in 10
minutes and gets the job done.</p>
<p>Quickly writing some lisp code to fix a specific problem is something I do very
often. Most of the time, it’s code I throw away as soon as the task is
completed, but from time to time it’s something generic enough to be reused
later, in which case I save it in my <code class="highlighter-rouge">emacs.d</code>, or make a proper package out of
it.</p>
<p>If you find this feature useful, you can grab it from
my
<a href="https://gitlab.petton.fr/nico/emacs.d/blob/master/local-packages/js2-unused.el">emacs.d</a>.</p>
</div>
<div id="disqus_thread"></div>
<script>
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'emacs-cafe';
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>
</article>
</div>
</main>
<footer class="site-footer">
<div class="wrapper">
<h2 class="footer-heading">Emacs café</h2>
<div class="footer-col-wrapper">
<div class="footer-col footer-col-1">
<ul class="contact-list">
<li>
Emacs café
</li>
<li><a href="mailto:nicolas@petton.fr">nicolas@petton.fr</a></li>
</ul>
</div>
<div class="footer-col footer-col-2">
<ul class="social-media-list">
<li>
<a href="https://github.com/NicolasPetton"><span class="icon icon--github"><svg viewBox="0 0 16 16" width="16px" height="16px"><path fill="#828282" d="M7.999,0.431c-4.285,0-7.76,3.474-7.76,7.761 c0,3.428,2.223,6.337,5.307,7.363c0.388,0.071,0.53-0.168,0.53-0.374c0-0.184-0.007-0.672-0.01-1.32 c-2.159,0.469-2.614-1.04-2.614-1.04c-0.353-0.896-0.862-1.135-0.862-1.135c-0.705-0.481,0.053-0.472,0.053-0.472 c0.779,0.055,1.189,0.8,1.189,0.8c0.692,1.186,1.816,0.843,2.258,0.645c0.071-0.502,0.271-0.843,0.493-1.037 C4.86,11.425,3.049,10.76,3.049,7.786c0-0.847,0.302-1.54,0.799-2.082C3.768,5.507,3.501,4.718,3.924,3.65 c0,0,0.652-0.209,2.134,0.796C6.677,4.273,7.34,4.187,8,4.184c0.659,0.003,1.323,0.089,1.943,0.261 c1.482-1.004,2.132-0.796,2.132-0.796c0.423,1.068,0.157,1.857,0.077,2.054c0.497,0.542,0.798,1.235,0.798,2.082 c0,2.981-1.814,3.637-3.543,3.829c0.279,0.24,0.527,0.713,0.527,1.437c0,1.037-0.01,1.874-0.01,2.129 c0,0.208,0.14,0.449,0.534,0.373c3.081-1.028,5.302-3.935,5.302-7.362C15.76,3.906,12.285,0.431,7.999,0.431z"/></svg>
</span><span class="username">NicolasPetton</span></a>
</li>
<li>
<a href="https://twitter.com/NicolasPetton"><span class="icon icon--twitter"><svg viewBox="0 0 16 16" width="16px" height="16px"><path fill="#828282" d="M15.969,3.058c-0.586,0.26-1.217,0.436-1.878,0.515c0.675-0.405,1.194-1.045,1.438-1.809c-0.632,0.375-1.332,0.647-2.076,0.793c-0.596-0.636-1.446-1.033-2.387-1.033c-1.806,0-3.27,1.464-3.27,3.27 c0,0.256,0.029,0.506,0.085,0.745C5.163,5.404,2.753,4.102,1.14,2.124C0.859,2.607,0.698,3.168,0.698,3.767 c0,1.134,0.577,2.135,1.455,2.722C1.616,6.472,1.112,6.325,0.671,6.08c0,0.014,0,0.027,0,0.041c0,1.584,1.127,2.906,2.623,3.206 C3.02,9.402,2.731,9.442,2.433,9.442c-0.211,0-0.416-0.021-0.615-0.059c0.416,1.299,1.624,2.245,3.055,2.271 c-1.119,0.877-2.529,1.4-4.061,1.4c-0.264,0-0.524-0.015-0.78-0.046c1.447,0.928,3.166,1.469,5.013,1.469 c6.015,0,9.304-4.983,9.304-9.304c0-0.142-0.003-0.283-0.009-0.423C14.976,4.29,15.531,3.714,15.969,3.058z"/></svg>
</span><span class="username">NicolasPetton</span></a>
</li>
</ul>
</div>
<div class="footer-col footer-col-3">
<p>A blog about Emacs, mostly focused on JavaScript development, by Nicolas Petton.
</p>
</div>
</div>
</div>
</footer>
</body>
</html>