<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel Reed</title>
    <description>The latest articles on DEV Community by Daniel Reed (@kwaimind).</description>
    <link>https://dev.to/kwaimind</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F665576%2Fe35c904a-4965-4ab5-8275-07e5d0446b83.jpeg</url>
      <title>DEV Community: Daniel Reed</title>
      <link>https://dev.to/kwaimind</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kwaimind"/>
    <language>en</language>
    <item>
      <title>How to write custom ESLint plugins</title>
      <dc:creator>Daniel Reed</dc:creator>
      <pubDate>Mon, 10 Jan 2022 15:19:18 +0000</pubDate>
      <link>https://dev.to/kwaimind/how-to-write-custom-eslint-plugins-173k</link>
      <guid>https://dev.to/kwaimind/how-to-write-custom-eslint-plugins-173k</guid>
      <description>&lt;p&gt;Until recently, there were two technologies that I didn’t understand. Crypto and ESLint plugins. Today I finally understood ESLint plugins.&lt;/p&gt;

&lt;p&gt;I've been wanting to make a custom ESLint plugin for a few months to see how I could customize my developer experience. I want to share my experience in learning about them, and give a guide on how you can build your own plugins in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;My team and I have been working on a client project, and a few months back we set some TypeScript code style conventions that we felt would help us manage some of our interfaces, types, and styled components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interfaces should start with the letter &lt;code&gt;I&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Types should start with the letter &lt;code&gt;T&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Styled components should start with the letter &lt;code&gt;S&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our belief is that this will help us and other developers know exactly what type a type is when using it throughout a codebase. Deciding this is one thing. Maintaining it is another and this left us with 2 options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remember this rule and fix it in code reviews when we see mistakes&lt;/li&gt;
&lt;li&gt;Set up an ESLint rule to check this for us automatically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, I took this as an opportunity to finally learn how to build custom ESLint plugins, and deliver a solution to our dev team.&lt;/p&gt;

&lt;h2&gt;
  
  
  My brief
&lt;/h2&gt;

&lt;p&gt;My plugin idea was simple. It should analyze TypeScript interfaces and types, and ensure they start with a capital &lt;code&gt;I&lt;/code&gt; or capital &lt;code&gt;T&lt;/code&gt;. It should also analyze styled components and ensure they start with a capital &lt;code&gt;S&lt;/code&gt;. Not only should it warn users when it finds a mistake, it should offer code solutions to fix these tedious tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  ESLint + Abstract Syntax Trees (ASTs)
&lt;/h2&gt;

&lt;p&gt;To understand ESLint, we need to take a step back and understand a bit more about how ESLint works. The basics are that ESLint needs to parse your code into something called an Abstract Syntax Tree, which is a representation of your code, it’s definitions, and values. If you want to learn more about how compilers and ESLint break code down into understandable chunks, &lt;a href="https://www.twilio.com/blog/abstract-syntax-trees" rel="noopener noreferrer"&gt;Twillio has a great guide&lt;/a&gt; on the computer science behind that. &lt;/p&gt;

&lt;h2&gt;
  
  
  Building your plugin
&lt;/h2&gt;

&lt;p&gt;To keep things simple, this will be a guide on building a ESLint plugin that targets TypeScript interfaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Understanding our code
&lt;/h3&gt;

&lt;p&gt;First up is finding a way to capture all interfaces in our code and finding their name (or Identifier). This will allow us to then verify that the interface name follows our convention of starting with a capital &lt;code&gt;I&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To visualize an abstract syntax tree, we can use a tool called AST explorer. &lt;a href="https://astexplorer.net/#/gist/8f56695db6eb77b7e5eff3f86d746322/latest" rel="noopener noreferrer"&gt;Here is an example link&lt;/a&gt; to get you started. You will see an AST generated on the right, and although this looks like madness, it’s actually pretty understandable. Click around in the right hand "tree" window, and open the &lt;code&gt;body&lt;/code&gt; data block. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://astexplorer.net/#/gist/8f56695db6eb77b7e5eff3f86d746322/latest" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmmgxpza0wzl2f5wjd9ij.png" alt="ast explorer"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Basically what we have now is some data on how a compiler might understand the code you have written. We have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;InterfaceDeclaration&lt;/code&gt;: the type of the interface&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Identifier&lt;/code&gt;: the identity of the interface (AppProps in this case)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ObjectTypeAnnotation&lt;/code&gt;: the content of the interface&lt;/li&gt;
&lt;li&gt;And more data on where the code is in the editor&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is great. Now we can understand how to catch all interfaces and then check their identifier names.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Build our transform rule
&lt;/h3&gt;

&lt;p&gt;Now we can start to build a solution. When thinking about ESLint plugins, you can think of it in 2 parts. A &lt;strong&gt;“listener”&lt;/strong&gt; that checks for a match, and a &lt;strong&gt;“responder”&lt;/strong&gt; that sends an error/warning and (maybe) offers a code solution. AST explorer has an editor that allows you to write these “listeners” and “responders”, and see how ESLint might use them.&lt;/p&gt;

&lt;p&gt;First of all, in the menu at the top of the page, make sure the button next to “JavaScript” is set to &lt;code&gt;babel-eslint&lt;/code&gt;. Then click on the “Transform” button and pick &lt;code&gt;ESLint v4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the transform window, you should see some sample code. Read through it and it should explain most of how ESLint transforms work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A rule is an object with a series of “listener” keys to match (in this example a &lt;code&gt;TemplateLiteral&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;When a node is matched, a function is fired and returns a context report with a message and (optional) code fix. This is sent back to the user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using this knowledge, we can build a solution for our plugin. Replace &lt;code&gt;TemplateLiteral&lt;/code&gt; with the type of an interface (&lt;code&gt;InterfaceDeclaration&lt;/code&gt;) and you should now see a warning thrown in the console on the right. That’s the basics and now we have a demo transformer working.&lt;/p&gt;

&lt;p&gt;Now we need to write a real solution. Let’s add some basic logic that checks if the first letter of the interface id is the letter I:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;InterfaceDeclaration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Interfaces must start with a capital I&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We should still see the error message. Add the letter &lt;code&gt;I&lt;/code&gt; before &lt;code&gt;AppProps&lt;/code&gt; and the error should disappear. Great. Now we have a working rule. Test it with some valid and invalid examples to verify things are working as expected. It might be easier to test these examples one at a time:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;preview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We now have everything we need to build a plugin for our team and the open source community to use. &lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Build our project
&lt;/h3&gt;

&lt;p&gt;Building a plugin package is straightforward, using the Yeoman ESLint generator: &lt;a href="https://github.com/eslint/generator-eslint#readme" rel="noopener noreferrer"&gt;https://github.com/eslint/generator-eslint#readme&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install the package:&lt;br&gt;
&lt;code&gt;npm i -g generator-eslint&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run the CLI and follow the instructions:&lt;br&gt;
&lt;code&gt;yo eslint:plugin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You will also need to install the TypeScript parser:&lt;br&gt;
&lt;code&gt;npm i @typescript-eslint/parser --dev&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Make a new file in the &lt;code&gt;lib/rules&lt;/code&gt; directory called &lt;code&gt;interfaces.js&lt;/code&gt; and add this boilerplate:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;suggestion&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enforcing the prefixing of interfaces&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;TSInterfaceDeclaration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Interfaces must start with a capital I&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have a meta object with some details about the rule, useful for documentation&lt;/li&gt;
&lt;li&gt;We’ve replaced our &lt;code&gt;InterfaceDeclaration&lt;/code&gt; “listener” for a &lt;code&gt;TSInterfaceDeclaration&lt;/code&gt; (see below)&lt;/li&gt;
&lt;li&gt;We have a create function that contains the transformer we made earlier&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why did I replace &lt;code&gt;InterfaceDeclaration&lt;/code&gt; for &lt;code&gt;TSInterfaceDeclaration&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;babel-eslint&lt;/code&gt; plugin doesn’t parse TypeScript but does work in the AST explorer. Using the @typescript-eslint/parser ensures we are parsing correctly, and we will add that as our parser in the next steps. This took me 2 hours to debug when writing unit tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, let’s add a unit test. Inside the &lt;code&gt;tests/lib/rules&lt;/code&gt; directory, add a file called &lt;code&gt;interfaces.test.js&lt;/code&gt; and add this boilerplate:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../../../lib/rules/interfaces&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RuleTester&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eslint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;RuleTester&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;RuleTester&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDefaultConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;parserOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ecmaVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sourceType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;module&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// eslint-disable-next-line node/no-unpublished-require&lt;/span&gt;
  &lt;span class="na"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@typescript-eslint/parser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tester&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuleTester&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;tester&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rule: interfaces&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interface IAnotherInterface { preview: boolean; }&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;invalid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interface AnotherInterface { preview: boolean; }&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Interfaces must start with a capital I&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;interface IAnotherInterface { preview: boolean; }&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Most of this is the testing format recommended by the ESLint team. The main part here is adding the TypeScript parser, and adding a range of valid and invalid tests to assert. You can read more about unit testing in the ESLint docs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5: Adding code fixing
&lt;/h3&gt;

&lt;p&gt;Almost done. To add code fixing, simple add a fix function inside the content.report object:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;fix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fixer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;I&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Finally, make sure to write a unit test and asserts the output:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  code: "interface CustomProps extends AppProps { preview: boolean; }",
  errors: [{ message: "Interfaces must start with a capital I" }],
  output: "interface ICustomProps extends AppProps { preview: boolean; }",
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And that’s it. Now you are ready to push the plugin to npm or add it to your project locally.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fup8yw8lj4o9iz07j16hr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fup8yw8lj4o9iz07j16hr.gif" alt="ESLint plugin fixing an interface"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;If you're interested, you should look into how to catch interfaces where the keyword already starts with the letter &lt;code&gt;I&lt;/code&gt;, like &lt;code&gt;InfoBoxProps&lt;/code&gt;. Also, this plugin needs better support for edge cases with odd interface names like &lt;code&gt;infobox&lt;/code&gt; or &lt;code&gt;idProps&lt;/code&gt; since our matching won’t catch those right now.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Wait, why isn’t the plugin written in TypeScript too&lt;br&gt;
Good question. It can be, but to keep things simple I opted to just build it in vanilla JS. If I’m adding a lot more rules or working on this project with others, then switching to a TypeScript toolchain would be a great idea&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This might be the 4th time I’ve tried to build an ESLint plugin. When doing my research, I found the API docs and most of the guides and tutorials written by others really hard to read and understand, even for the most simple plugin ideas. Hopefully this guide will help you get started.&lt;/p&gt;

&lt;p&gt;You can see my repo with my interface examples, and 2 more I made (one for types and one for styled components) here.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kwaimind" rel="noopener noreferrer"&gt;
        kwaimind
      &lt;/a&gt; / &lt;a href="https://github.com/kwaimind/eslint-plugin-prefix-types" rel="noopener noreferrer"&gt;
        eslint-plugin-prefix-types
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An ESLint plugin to enforce the prefixing of interfaces, types, and styled components.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Understanding nouns with tinyplural </title>
      <dc:creator>Daniel Reed</dc:creator>
      <pubDate>Mon, 27 Dec 2021 11:25:28 +0000</pubDate>
      <link>https://dev.to/kwaimind/understanding-nouns-with-tinyplural-46m8</link>
      <guid>https://dev.to/kwaimind/understanding-nouns-with-tinyplural-46m8</guid>
      <description>&lt;p&gt;Hey there, I'm Daniel and I'm a software engineer working in Stockholm, Sweden.&lt;/p&gt;

&lt;p&gt;A while back I was working on a settings page and had to add a section letting users know when their subscription would expire. This was pretty straightforward so I added:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;// fetched from db&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Your subscription will end in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;days&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;days&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Nice and simple. But super annoying. I've always been triggered by apps that just hard code something like &lt;code&gt;2 day(s)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I got thinking and wanted to make an npm package that could easily find the plural for any English noun. This was the start of my first open source project &lt;code&gt;tinyplural&lt;/code&gt;.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/kwaimind" rel="noopener noreferrer"&gt;
        kwaimind
      &lt;/a&gt; / &lt;a href="https://github.com/kwaimind/tinyplural" rel="noopener noreferrer"&gt;
        tinyplural
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      ✍ A tiny pluralizer for English nouns
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;To do this I used &lt;a href="https://github.com/formium/tsdx" rel="noopener noreferrer"&gt;TSDX&lt;/a&gt; which helps scaffold TypeScript libs and started to research how plurals work in English for nouns.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A noun is a word that refers to a thing (book), a person (Betty Crocker), an animal (cat), a place (Omaha), a quality (softness), an idea (justice), or an action (yodeling). It's usually a single word, but not always: cake, shoes, school bus, and time and a half are all nouns.&lt;/p&gt;

&lt;p&gt;A word denoting more than one, or (in languages with dual number) more than two.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://www.grammarly.com/blog/plural-nouns/" rel="noopener noreferrer"&gt;Grammerly&lt;/a&gt; had a great article that broke down English nouns into some core grammar rules that I could build functions around.&lt;/p&gt;

&lt;h2&gt;
  
  
  First version
&lt;/h2&gt;

&lt;p&gt;In my first version, I made a function for each of these rules. This was inefficient but easy to test and understand. Most of the rules of English are based on the last few letters of the word, so my solution here was to use some RegEx to test if a string ended with specific letters and then return a noun with the plural. Once this worked I refactored everything into 4 core functions that could do all the work based on some find and replace keys or a callback.&lt;/p&gt;

&lt;p&gt;The function works by passing in a singular noun (i.e. not the plural version) and a number&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;tinyplural("day", 2);&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;English, like many languages, has a few nouns that don't follow any rules known as /irregular nouns/. Some follow a different pattern and others don't change. i.e., 1 fish, 2 fish, 1 child, 2 children. For these, I added an array that we check and if there is a match, we return the same string or the irregular version. &lt;/p&gt;

&lt;h2&gt;
  
  
  Focusing on speed
&lt;/h2&gt;

&lt;p&gt;Since this is to be used as a 3rd party lib, I wanted to make sure things were fast and simple and so I added some performance optimisations to help.&lt;/p&gt;

&lt;p&gt;First up, this package is only checking for plurals, so we escape early if there is only 1 of a noun.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;tinyplural&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// early escape, return original input&lt;/span&gt;
&lt;span class="nf"&gt;tinyplural&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// run the lookup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To manage all of the rules, I made an array of functions with their options. As soon as we have a match, we break and return the result.&lt;/p&gt;

&lt;p&gt;Finally, I added a cache map to memorize inputs and prevent recalculating the same result. If the function is called with the same arguments, we return the cached version and skip any further calculations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// first time&lt;/span&gt;
&lt;span class="nf"&gt;tinyplural&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// run the lookup&lt;/span&gt;
&lt;span class="c1"&gt;// second time&lt;/span&gt;
&lt;span class="nf"&gt;tinyplural&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;day&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// check the cache map, return the previous result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  TSDX
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/jaredpalmer/tsdx" rel="noopener noreferrer"&gt;TSDX&lt;/a&gt; is a great package for npm packages like this and even bundles with &lt;a href="https://github.com/ai/size-limit/" rel="noopener noreferrer"&gt;&lt;code&gt;size-limit&lt;/code&gt;&lt;/a&gt; to check your gzipped final bundle size. Keeping a tiny package was really important to me so there are 0 dependencies and &lt;code&gt;size-limit&lt;/code&gt; made me realise that simplifying all of my checking functions into 1 or 2 core functions that took options would be a better strategy and help reduce the code size. &lt;/p&gt;

&lt;h2&gt;
  
  
  Future ideas
&lt;/h2&gt;

&lt;p&gt;I’m pretty happy with the outcome so far and have a few ideas I want to try and continue building on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Working with other latin-based languages based on a given local&lt;/li&gt;
&lt;li&gt;Working on a better release pipeline with Github actions + npm&lt;/li&gt;
&lt;li&gt;Testing in a production app&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>opensource</category>
      <category>typescript</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
