erlang-language-platform/docs/contributing/code-actions/index.html
facebook-github-bot 075ee666d6 deploy: 23cc244f59
2024-08-16 13:26:59 +00:00

17 lines
No EOL
58 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" dir="ltr" class="docs-wrapper docs-doc-page docs-version-current plugin-docs plugin-id-default docs-doc-id-contributing/code-actions" data-has-hydrated="false">
<head>
<meta charset="UTF-8">
<meta name="generator" content="Docusaurus v2.4.3">
<title data-rh="true">Code Actions (a.k.a. Assists) | ELP</title><meta data-rh="true" name="viewport" content="width=device-width,initial-scale=1"><meta data-rh="true" name="twitter:card" content="summary_large_image"><meta data-rh="true" property="og:url" content="https://whatsapp.github.io/erlang-language-platform/docs/contributing/code-actions/"><meta data-rh="true" name="docusaurus_locale" content="en"><meta data-rh="true" name="docsearch:language" content="en"><meta data-rh="true" name="docusaurus_version" content="current"><meta data-rh="true" name="docusaurus_tag" content="docs-default-current"><meta data-rh="true" name="docsearch:version" content="current"><meta data-rh="true" name="docsearch:docusaurus_tag" content="docs-default-current"><meta data-rh="true" property="og:title" content="Code Actions (a.k.a. Assists) | ELP"><meta data-rh="true" name="description" content="Code actions, also known as assists, are small local refactorings, often rendered in the text editor using a light bulb icon (💡). They are triggered by either clicking the light bulb icon in the editor or by using a shortcut."><meta data-rh="true" property="og:description" content="Code actions, also known as assists, are small local refactorings, often rendered in the text editor using a light bulb icon (💡). They are triggered by either clicking the light bulb icon in the editor or by using a shortcut."><link data-rh="true" rel="icon" href="/erlang-language-platform/img/elp_icon_color.svg"><link data-rh="true" rel="canonical" href="https://whatsapp.github.io/erlang-language-platform/docs/contributing/code-actions/"><link data-rh="true" rel="alternate" href="https://whatsapp.github.io/erlang-language-platform/docs/contributing/code-actions/" hreflang="en"><link data-rh="true" rel="alternate" href="https://whatsapp.github.io/erlang-language-platform/docs/contributing/code-actions/" hreflang="x-default"><link rel="stylesheet" href="/erlang-language-platform/assets/css/styles.93dc32ac.css">
<link rel="preload" href="/erlang-language-platform/assets/js/runtime~main.0fde5a36.js" as="script">
<link rel="preload" href="/erlang-language-platform/assets/js/main.adadd7a5.js" as="script">
</head>
<body class="navigation-with-keyboard">
<script>!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){var t=null;try{t=new URLSearchParams(window.location.search).get("docusaurus-theme")}catch(t){}return t}()||function(){var t=null;try{t=localStorage.getItem("theme")}catch(t){}return t}();t(null!==e?e:"light")}()</script>
<div style="display: none; text-align: center; background-color: white; color: black;" id="internaldocs-banner"></div><div id="__docusaurus">
<div role="region" aria-label="Skip to main content"><a class="skipToContent_fXgn" href="#__docusaurus_skipToContent_fallback">Skip to main content</a></div><nav aria-label="Main" class="navbar navbar--fixed-top"><div class="navbar__inner"><div class="navbar__items"><button aria-label="Toggle navigation bar" aria-expanded="false" class="navbar__toggle clean-btn" type="button"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a class="navbar__brand" href="/erlang-language-platform/"><div class="navbar__logo"><img src="/erlang-language-platform/img/elp_logo_color.svg" alt="ELP Logo" class="themedImage_ToTc themedImage--light_HNdA"><img src="/erlang-language-platform/img/elp_logo_color.svg" alt="ELP Logo" class="themedImage_ToTc themedImage--dark_i4oU"></div></a><a aria-current="page" class="navbar__item navbar__link navbar__link--active" href="/erlang-language-platform/docs/get-started/">Get Started</a><a aria-current="page" class="navbar__item navbar__link navbar__link--active" href="/erlang-language-platform/docs/feature-gallery/">Feature Gallery</a><a aria-current="page" class="navbar__item navbar__link navbar__link--active" href="/erlang-language-platform/docs/contributing/">Contributing</a><a aria-current="page" class="navbar__item navbar__link navbar__link--active" href="/erlang-language-platform/docs/erlang-error-index/">Erlang Error Index</a></div><div class="navbar__items navbar__items--right"><a href="https://github.com/whatsapp/erlang-language-platform" target="_blank" rel="noopener noreferrer" class="navbar__item navbar__link">GitHub<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a><div class="toggle_vylO colorModeToggle_DEke"><button class="clean-btn toggleButton_gllP toggleButtonDisabled_aARS" type="button" disabled="" title="Switch between dark and light mode (currently light mode)" aria-label="Switch between dark and light mode (currently light mode)" aria-live="polite"><svg viewBox="0 0 24 24" width="24" height="24" class="lightToggleIcon_pyhR"><path fill="currentColor" d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"></path></svg><svg viewBox="0 0 24 24" width="24" height="24" class="darkToggleIcon_wfgR"><path fill="currentColor" d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path></svg></button></div><div class="searchBox_ZlJk"><div class="navbar__search"><span aria-label="expand searchbar" role="button" class="search-icon" tabindex="0"></span><input type="search" id="search_input_react" placeholder="Loading..." aria-label="Search" class="navbar__search-input search-bar" disabled=""></div></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div id="__docusaurus_skipToContent_fallback" class="main-wrapper mainWrapper_z2l0 docsWrapper_BCFX"><button aria-label="Scroll back to top" class="clean-btn theme-back-to-top-button backToTopButton_sjWU" type="button"></button><div class="docPage__5DB"><aside class="theme-doc-sidebar-container docSidebarContainer_b6E3"><div class="sidebarViewport_Xe31"><div class="sidebar_njMd"><nav aria-label="Docs sidebar" class="menu thin-scrollbar menu_SIkG"><ul class="theme-doc-sidebar-menu menu__list"><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist" aria-expanded="false" href="/erlang-language-platform/docs/get-started/">Get Started</a><button aria-label="Toggle the collapsible sidebar category &#x27;Get Started&#x27;" type="button" class="clean-btn menu__caret"></button></div></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/erlang-language-platform/docs/feature-gallery/">Feature Gallery</a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist menu__link--active" aria-expanded="true" href="/erlang-language-platform/docs/contributing/">Contributing</a><button aria-label="Toggle the collapsible sidebar category &#x27;Contributing&#x27;" type="button" class="clean-btn menu__caret"></button></div><ul style="display:block;overflow:visible;height:auto" class="menu__list"><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-2 menu__list-item"><a class="menu__link menu__link--active" aria-current="page" tabindex="0" href="/erlang-language-platform/docs/contributing/code-actions/">Code Actions (a.k.a. Assists)</a></li></ul></li><li class="theme-doc-sidebar-item-link theme-doc-sidebar-item-link-level-1 menu__list-item"><a class="menu__link" href="/erlang-language-platform/docs/architecture/">Architecture</a></li><li class="theme-doc-sidebar-item-category theme-doc-sidebar-item-category-level-1 menu__list-item menu__list-item--collapsed"><div class="menu__list-item-collapsible"><a class="menu__link menu__link--sublist" aria-expanded="false" href="/erlang-language-platform/docs/erlang-error-index/">Erlang Error Index</a><button aria-label="Toggle the collapsible sidebar category &#x27;Erlang Error Index&#x27;" type="button" class="clean-btn menu__caret"></button></div></li></ul></nav></div></div></aside><main class="docMainContainer_gTbr"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_VOVn"><div class="docItemContainer_Djhp"><article><nav class="theme-doc-breadcrumbs breadcrumbsContainer_Z_bl" aria-label="Breadcrumbs"><ul class="breadcrumbs" itemscope="" itemtype="https://schema.org/BreadcrumbList"><li class="breadcrumbs__item"><a aria-label="Home page" class="breadcrumbs__link" href="/erlang-language-platform/"><svg viewBox="0 0 24 24" class="breadcrumbHomeIcon_YNFT"><path d="M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z" fill="currentColor"></path></svg></a></li><li itemscope="" itemprop="itemListElement" itemtype="https://schema.org/ListItem" class="breadcrumbs__item"><a class="breadcrumbs__link" itemprop="item" href="/erlang-language-platform/docs/contributing/"><span itemprop="name">Contributing</span></a><meta itemprop="position" content="1"></li><li itemscope="" itemprop="itemListElement" itemtype="https://schema.org/ListItem" class="breadcrumbs__item breadcrumbs__item--active"><span class="breadcrumbs__link" itemprop="name">Code Actions (a.k.a. Assists)</span><meta itemprop="position" content="2"></li></ul></nav><div class="tocCollapsible_ETCw theme-doc-toc-mobile tocMobile_ITEo"><button type="button" class="clean-btn tocCollapsibleButton_TO0P">On this page</button></div><div class="theme-doc-markdown markdown"><h1>Code Actions (a.k.a. Assists)</h1><p><em>Code actions</em>, also known as <em>assists</em>, are small local refactorings, often rendered in the text editor using a light bulb icon (💡). They are triggered by either clicking the light bulb icon in the editor or by using a shortcut.</p><p>Code actions often provide the user with possible corrective actions right next to an error or warning (known as a <em>diagnostic</em> message using LSP jargon). They can also occur independently of diagnostics.</p><p>Here is an example of a <em>code action</em> prompting the user to <em>add an EDoc comment</em> for a function which lacks Erlang EDoc documentation.</p><p><img loading="lazy" alt="Code Action - Add Edoc" src="/erlang-language-platform/assets/images/code-action-add-edoc-00c08bb4c5326a4f4544e606890fd667.png" width="968" height="376" class="img_ev3q"></p><p>And this is what the code looks like after the suggestion has been applied:</p><p><img loading="lazy" alt="Code Action - Add Edoc Fix" src="/erlang-language-platform/assets/images/code-action-add-edoc-fix-41b68bc5f8f0af57dd2dcdc686e0d850.png" width="1502" height="326" class="img_ev3q"></p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-code-action-request">The <em>Code Action</em> request<a href="#the-code-action-request" class="hash-link" aria-label="Direct link to the-code-action-request" title="Direct link to the-code-action-request"></a></h2><p>Code actions are requested by the editor using the <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction" target="_blank" rel="noopener noreferrer">textDocument/codeAction</a> LSP request. Code action requests are handled by the <code>handlers::handle_code_action</code> function in the <code>elp</code> crate.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="adding-a-new-code-action">Adding a new code action<a href="#adding-a-new-code-action" class="hash-link" aria-label="Direct link to Adding a new code action" title="Direct link to Adding a new code action"></a></h2><p>In this section we will go through the process of adding a new code action from scratch. The code action (or <em>assist</em>) will suggest the user to delete a function, if it is deemed as unused by the Erlang compiler.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="creating-the-handler">Creating the handler<a href="#creating-the-handler" class="hash-link" aria-label="Direct link to Creating the handler" title="Direct link to Creating the handler"></a></h3><p>Let&#x27;s start by creating a new file named <code>delete_function.rs</code>, containing a single function declaration:</p><div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_Ktv7">crates/ide_assists/src/handlers/delete_function.rs</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">use crate::assist_context::{Assists, AssistContext};</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">pub(crate) fn delete_function(acc: &amp;mut Assists, ctx: &amp;AssistContext) -&gt; Option&lt;()&gt; {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> todo!()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Before we can start implementing our code action, there&#x27;s one more thing we need to do: ensure our new function is invoked by adding it to the list of <em>ELP assists</em>. Open the <code>crates/ide_assists/src/lib.rs</code> file and amend the list of handlers:</p><div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_Ktv7">crates/ide_assists/src/lib.rs</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">mod handlers {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> [...]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> mod delete_function</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> [...]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> pub(crate) fn all() -&gt; &amp;&#x27;static [Handler] {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> &amp;[</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> [...]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> delete_function:delete_function,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> [...]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="adding-a-test-case">Adding a test case<a href="#adding-a-test-case" class="hash-link" aria-label="Direct link to Adding a test case" title="Direct link to Adding a test case"></a></h3><p>The easiest way to verify our new code action behaves in the expected way is to start with a test case. ELP allows us to write tests in a very intuitive and straightforward way.</p><p>Add the following to the <code>delete_function.rs</code> file:</p><div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_Ktv7">crates/ide_assists/src/handlers/delete_function.rs</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">#[cfg(test)]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">mod tests {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> use expect_test::expect;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> use super::*;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> use crate::tests::*;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> #[test]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> fn test_delete_unused_function() {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> check_assist(</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> delete_function,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> &quot;Remove the unused function `heavy_calculations/1`&quot;,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> r#&quot;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> -module(life).</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> heavy_cal~culations(X) -&gt;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> %% ^^^^^^^^^^^^^^^^^^^ 💡 L1230: Function heavy_calculations/1 is unused</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> X.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> meaning() -&gt;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> 42.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">&quot;#,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> expect![[</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> r#&quot;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> -module(life).</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> meaning() -&gt;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> 42.</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> &quot;#]],</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> )</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>There is a lot happening here, so let&#x27;s go through the code. We are defining a new test, named <code>test_delete_unused_function</code>, which uses an auxiliary function (<code>check_assist</code>) to verify that a given assist behaves as expected.</p><p>The <code>check_assist</code> function takes 4 arguments:</p><ul><li>The assist <em>handler</em> (<code>delete_function</code>)</li><li>A <em>label</em> for the assist</li><li>An input fixture representing what the code looks like <strong>before</strong> a fix is applied</li><li>An output fixture (wrapped in an <code>expect</code> macro) showing what the code looks like <strong>after</strong> a fix is applied</li></ul><p>The <code>~</code> in the first snippet represents the cursor position. We are asserting that - given a diagnostic message pointing to the unused function - if the user triggers the respective code action when the cursor is hovering the function name range, the unused function gets deleted.</p><p>Let&#x27;s try running the test, it should fail with a <em>not yet implemented</em> error:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">$ cargo test --package elp_ide_assists --lib -- handlers::delete_function::tests::test_delete_unused_function --exact --nocapture</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[...]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">---- handlers::delete_function::tests::test_delete_unused_function stdout ----</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">thread &#x27;handlers::delete_function::tests::test_delete_unused_function&#x27; panicked at &#x27;not yet implemented&#x27;, crates/ide_assists/src/handlers/delete_function.rs:21:5</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[...]</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="diagnostic-annotations-and-error-codes">Diagnostic Annotations and Error Codes<a href="#diagnostic-annotations-and-error-codes" class="hash-link" aria-label="Direct link to Diagnostic Annotations and Error Codes" title="Direct link to Diagnostic Annotations and Error Codes"></a></h3><p>Before starting with the actual implementation, let&#x27;s for a second go back to the syntax we used to specify the <em>unused function</em> diagnostic:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">%% ^^^^^^^^^^^^^^^^^^^ 💡 L1230: Function heavy_calculations/1 is unused</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>This is a test <em>annotation</em> which is used by the ELP testing framework to populate the &quot;context&quot; which is passed to our handler. This is a way to simulate diagnostics coming from external sources (such as the Erlang compiler or a linter), which would be received by the Language Server as part of a <code>textDocument/codeAction</code> request.</p><p>The annotation has the following format:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">[\s]%% [^]* 💡 CODE: MESSAGE</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Essentially, a number of spaces, followed by the <code>%%</code> which resembles an Erlang comment, a light bulb, a <em>code</em> identifying the diagnostic type and a string message. The <em>code</em> is an <em>unofficial error</em> code which is emitted by both ELP&#x27;s <em>Erlang Service</em> (see the <code>erlang_service:make_code/2</code> function in <code>erlang_service/src/erlang_service.erl</code>) and by the <a href="https://github.com/erlang-ls/erlang_ls/" target="_blank" rel="noopener noreferrer">Erlang LS</a> language server. The idea is to eventually standardize Erlang error messages and to build what, in the end, should be similar to the <a href="https://doc.rust-lang.org/error-index.html" target="_blank" rel="noopener noreferrer">Rust</a> or <a href="https://errors.haskell.org/" target="_blank" rel="noopener noreferrer">Haskell</a> error indexes. In our case, <code>L1230</code> is the error corresponding to the <code>unused_function</code> diagnostic. The <em>message</em> is a free text string that accompanies the diagnostic.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="matching-on-the-diagnostic-error-code">Matching on the diagnostic error code<a href="#matching-on-the-diagnostic-error-code" class="hash-link" aria-label="Direct link to Matching on the diagnostic error code" title="Direct link to Matching on the diagnostic error code"></a></h3><p>To be able to match the <code>L1230</code> error code, we need to add a new variant to the <code>AssistContextDiagnosticCode</code> enum. Open the <code>crates/ide_db/src/assists.rs</code> file and include the new error code. Don&#x27;t forget to map it to the <code>L1230</code> string.</p><div class="language-rust codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockTitle_Ktv7">crates/ide_db/src/assists.rs</div><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-rust codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">pub enum AssistContextDiagnosticCode {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> UnusedFunction, // &lt;--- Add this</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> [...]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> UnusedVariable,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">impl FromStr for AssistContextDiagnosticCode {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> type Err = String;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> fn from_str(s: &amp;str) -&gt; Result&lt;Self, Self::Err&gt; {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> match s {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> &quot;L1230&quot; =&gt; Ok(AssistContextDiagnosticCode::UnusedFunction), // &lt;--- Add this</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> [...]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> &quot;L1268&quot; =&gt; Ok(AssistContextDiagnosticCode::UnusedVariable),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> unknown =&gt; Err(format!(&quot;Unknown AssistContextDiagnosticCode: &#x27;{unknown}&#x27;&quot;)),</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>We are all set. Time to implement the <code>delete_function</code> function!</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-implementation">The implementation<a href="#the-implementation" class="hash-link" aria-label="Direct link to The implementation" title="Direct link to The implementation"></a></h3><p>Let&#x27;s look at our function again.</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">pub(crate) fn delete_function(acc: &amp;mut Assists, ctx: &amp;AssistContext) -&gt; Option&lt;()&gt; {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> todo!()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>We have two input arguments: a mutable <em>accumulator</em> which contains the list of code actions (or <em>assists</em>) which we want to return and a <em>context</em>, from which we can extract diagnostics.</p><p>The following code iterates through the list of diagnostics and, for each diagnostic matching the <code>UnusedFunction</code> kind, prints the diagnostic for debugging purposes. We return <code>Some(())</code> to comply with the function signature.</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">use elp_ide_db::assists::AssistContextDiagnosticCode;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[...]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">pub(crate) fn delete_function(acc: &amp;mut Assists, ctx: &amp;AssistContext) -&gt; Option&lt;()&gt; {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> for d in ctx.diagnostics {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> if let AssistContextDiagnosticCode::UnusedFunction = d.code {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> dbg!(d);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> todo!()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Some(())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[...]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>If we run the test, we can see what a diagnostic looks like:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">$ cargo test --package elp_ide_assists --lib -- handlers::delete_function::tests::test_delete_unused_function --exact --nocapture</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[...]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">running 1 test</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[crates/ide_assists/src/handlers/delete_function.rs:25] d = AssistContextDiagnostic {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> code: UnusedFunction,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> message: &quot;Function heavy_calculations/1 is unused&quot;,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> range: 24..40,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[...]</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>The diagnostic contains the error code and message, together with its range. What we want to do is:</p><ul><li>Find the function declaration which is pointed by the diagnostic range</li><li>Create a code action to remove the function declaration and add it to the accumulator</li></ul><p>How do we find the element which the range covers? Context to the rescue! There&#x27;s a handy <code>find_node_at_custom_offset</code> function we can use. The <em>offset</em> here indicates the number of bytes from the beginning of the file. We can use the beginning of the diagnostic range for our purposes.</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">let function_declaration: ast::FunDecl = ctx.find_node_at_custom_offset::&lt;ast::FunDecl&gt;(d.range.start())?;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">let function_range = function_declaration.syntax().text_range();</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Let&#x27;s extract the function name/arity and produce a nice message for the user:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">let function_name = function_declaration.name()?;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">let function_arity = function_declaration.arity_value()?;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">let message = format!(&quot;Remove the unused function `{function_name}/{function_arity}`&quot;);</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>With the information we have, we can now create a new code action and add it to the accumulator:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">let id = AssistId(&quot;delete_function&quot;, AssistKind::QuickFix);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">let function_range = function_declaration.syntax().text_range();</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">acc.add(id,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> message,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> function_range,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> |builder| {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> builder.edit_file(ctx.frange.file_id);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> builder.delete(function_range)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">);</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>The <code>add</code> function takes four arguments:</p><ul><li>An internal <code>AssistId</code> made of a unique string (the <code>&quot;delete_function&quot;</code> string in our case) and a <code>Kind</code>. We are specifying <code>QuickFix</code> in our case, but have a look to the <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#codeActionKind" target="_blank" rel="noopener noreferrer">LSP specifications</a> to get a list of the available kinds.</li><li>A message which will be rendered to the user (<code>&quot;Delete the unused function: [FUNCTION_NAME]&quot;</code>)</li><li>The range of the function. Notice how the range we got from the diagnostic was covering only the <em>name</em> of the function, but we need to delete the whole function, so we need to pass the full range.</li><li>A function which takes a <code>builder</code> as its input and uses it to manipulate the source file. Here we are saying that we want to edit the current file (we extract the <code>file_id</code> from the <code>ctx</code> context) and that we simply want to delete the range of the function declaration.</li></ul><p>Yes. It&#x27;s as simple as that. For completeness, here is the full function implementation:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">pub(crate) fn delete_function(acc: &amp;mut Assists, ctx: &amp;AssistContext) -&gt; Option&lt;()&gt; {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> for d in ctx.diagnostics {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> if let AssistContextDiagnosticCode::UnusedFunction = d.code {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> let function_declaration: ast::FunDecl =</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ctx.find_node_at_custom_offset::&lt;ast::FunDecl&gt;(d.range.start())?;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> let function_name = function_declaration.name()?;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> let function_arity = function_declaration.arity_value()?;</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> let function_range = function_declaration.syntax().text_range();</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> let id = AssistId(&quot;delete_function&quot;, AssistKind::QuickFix);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> let message = format!(&quot;Remove the unused function `{function_name}/{function_arity}`&quot;);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> acc.add(id, message, function_range, |builder| {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> builder.edit_file(ctx.frange.file_id);</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> builder.delete(function_range)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> });</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Some(())</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>You can look at existing assists for more complex manipulation examples.</p><h1>Try it yourself</h1><p>What we wrote is a unit test, but there&#x27;s nothing better than checking ourselves the behaviour in the IDE.</p><p>Compile the <code>elp</code> executable:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">cargo build</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Then visit the Erlang extension settings page and edit the <code>elp.path</code> value to point to the newly built executable, which should reside in:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">./debug/elp</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg viewBox="0 0 24 24" class="copyButtonIcon_y97N"><path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg viewBox="0 0 24 24" class="copyButtonSuccessIcon_LjdS"><path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Open VS Code (or reload the window if you have it already open) and visit an Erlang file which contains an unused function. You should see something like:</p><p><img loading="lazy" alt="Code Action - Remove Function" src="/erlang-language-platform/assets/images/code-action-remove-function-17500a18e85501d12d483dc13df76153.png" width="880" height="467" class="img_ev3q"></p><p>If that worked, congratulations! You managed to write your first ELP code action!</p></div></article><nav class="pagination-nav docusaurus-mt-lg" aria-label="Docs pages"><a class="pagination-nav__link pagination-nav__link--prev" href="/erlang-language-platform/docs/contributing/"><div class="pagination-nav__sublabel">Previous</div><div class="pagination-nav__label">Contributing</div></a><a class="pagination-nav__link pagination-nav__link--next" href="/erlang-language-platform/docs/architecture/"><div class="pagination-nav__sublabel">Next</div><div class="pagination-nav__label">Architecture</div></a></nav></div></div><div class="col col--3"><div class="tableOfContents_bqdL thin-scrollbar theme-doc-toc-desktop"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#the-code-action-request" class="table-of-contents__link toc-highlight">The <em>Code Action</em> request</a></li><li><a href="#adding-a-new-code-action" class="table-of-contents__link toc-highlight">Adding a new code action</a><ul><li><a href="#creating-the-handler" class="table-of-contents__link toc-highlight">Creating the handler</a></li><li><a href="#adding-a-test-case" class="table-of-contents__link toc-highlight">Adding a test case</a></li><li><a href="#diagnostic-annotations-and-error-codes" class="table-of-contents__link toc-highlight">Diagnostic Annotations and Error Codes</a></li><li><a href="#matching-on-the-diagnostic-error-code" class="table-of-contents__link toc-highlight">Matching on the diagnostic error code</a></li><li><a href="#the-implementation" class="table-of-contents__link toc-highlight">The implementation</a></li></ul></li></ul></div></div></div></div></main></div></div><footer class="footer footer--dark"><div class="container container-fluid"><div class="row footer__links"><div class="col footer__col"><div class="footer__title">Docs</div><ul class="footer__items clean-list"><li class="footer__item"><a class="footer__link-item" href="/erlang-language-platform/docs/get-started/">Get Started</a></li><li class="footer__item"><a class="footer__link-item" href="/erlang-language-platform/docs/architecture/">Architecture</a></li><li class="footer__item"><a class="footer__link-item" href="/erlang-language-platform/docs/erlang-error-index/">Erlang Error Index</a></li></ul></div><div class="col footer__col"><div class="footer__title">Community</div><ul class="footer__items clean-list"><li class="footer__item"><a href="https://github.com/whatsapp/erlang-language-platform/issues" target="_blank" rel="noopener noreferrer" class="footer__link-item">GitHub Issues<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li></ul></div><div class="col footer__col"><div class="footer__title">More</div><ul class="footer__items clean-list"><li class="footer__item"><a href="https://github.com/whatsapp/erlang-language-platform" target="_blank" rel="noopener noreferrer" class="footer__link-item">GitHub<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://github.com/WhatsApp/erlang-language-platform/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer" class="footer__link-item">Contributing<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://github.com/WhatsApp/erlang-language-platform/blob/main/CODE_OF_CONDUCT.md" target="_blank" rel="noopener noreferrer" class="footer__link-item">Code Of Conduct<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://opensource.fb.com/legal/terms" target="_blank" rel="noopener noreferrer" class="footer__link-item">Terms of Use<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li><li class="footer__item"><a href="https://opensource.fb.com/legal/privacy" target="_blank" rel="noopener noreferrer" class="footer__link-item">Privacy Policy<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_nPIU"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></a></li></ul></div></div><div class="footer__bottom text--center"><div class="footer__copyright">Copyright © 2024 Meta Platforms, Inc. Built with Docusaurus.</div></div></div></footer></div>
<script src="/erlang-language-platform/assets/js/runtime~main.0fde5a36.js"></script>
<script src="/erlang-language-platform/assets/js/main.adadd7a5.js"></script>
</body>
</html>