<?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: r7kamura</title>
    <description>The latest articles on DEV Community by r7kamura (@r7kamura).</description>
    <link>https://dev.to/r7kamura</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%2F41287%2F0e7a1868-be1f-42e5-b96d-a257e1f6c7c4.jpg</url>
      <title>DEV Community: r7kamura</title>
      <link>https://dev.to/r7kamura</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/r7kamura"/>
    <language>en</language>
    <item>
      <title>Use ruby-lsp plugins without modifying the project's Gemfile</title>
      <dc:creator>r7kamura</dc:creator>
      <pubDate>Sat, 15 Jun 2024 17:12:52 +0000</pubDate>
      <link>https://dev.to/r7kamura/use-ruby-lsp-plugins-without-modifying-the-projects-gemfile-4i93</link>
      <guid>https://dev.to/r7kamura/use-ruby-lsp-plugins-without-modifying-the-projects-gemfile-4i93</guid>
      <description>&lt;p&gt;First, avoid including editor-specific configuration files in the project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .gitignore OR .git/info/exclude&lt;/span&gt;
.vscode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, prepare a Gemfile for ruby-lsp in the gitignored directory. If .ruby-lsp/Gemfile already exists, you can copy and paste and edit it. In this example, I will introduce a new plugin called ruby-lsp-rspec.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .vscode/Gemfile&lt;/span&gt;
&lt;span class="n"&gt;eval_gemfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expand_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'../Gemfile'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;__dir__&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="ss"&gt;:development&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"ruby-lsp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"ruby-lsp-rails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"ruby-lsp-rspec"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;require: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, change the configuration of ruby-lsp to specify the path to the Gemfile. This may be set from the GUI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vscode/settings.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rubyLsp.bundleGemfile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".vscode/Gemfile"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reloading the workspace will reflect the settings; you can also use VSCode's reloadWindow command. Currently, ruby-lsp seems to have a bug that an error occurs if this setting is changed at startup, in which case you can reload it twice.&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>GitHub CLI on devcontainer</title>
      <dc:creator>r7kamura</dc:creator>
      <pubDate>Tue, 16 Aug 2022 08:23:35 +0000</pubDate>
      <link>https://dev.to/r7kamura/github-cli-on-devcontainer-3od7</link>
      <guid>https://dev.to/r7kamura/github-cli-on-devcontainer-3od7</guid>
      <description>&lt;p&gt;To use the &lt;a href="https://cli.github.com/"&gt;GitHub CLI&lt;/a&gt; (a.k.a. &lt;code&gt;gh&lt;/code&gt;) on devcontainer, you need to pass credentials for that.&lt;/p&gt;

&lt;p&gt;If you run &lt;code&gt;gh auth login&lt;/code&gt; on the host side, the credentials will be stored at &lt;code&gt;~/.config/gh/hosts.yml&lt;/code&gt;, so mounting it on the container side will work.&lt;/p&gt;

&lt;p&gt;Maybe it can be difficult to include this setting in &lt;code&gt;docker-compose.yml&lt;/code&gt; since it is the developer's preference whether to use &lt;code&gt;gh&lt;/code&gt; and devcontainer or not. In such case, it would be nice to add that setting to &lt;code&gt;docker-compose.override.yml&lt;/code&gt;, and ignore these files from Git.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.override.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rails&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;~/.config/gh/hosts.yml:/root/.config/gh/hosts.yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .gitignore or .git/info/exclude&lt;/span&gt;
docker-compose.override.yml
.devcontainer/devcontainer.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normally, &lt;code&gt;docker-compose&lt;/code&gt; command will automatically merge &lt;code&gt;docker-compose.override.yml&lt;/code&gt; into &lt;code&gt;docker-compose.yml&lt;/code&gt;, but in this case you need to specify it explicitly as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.devcontainer/devcontainer.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Rails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dockerComposeFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"../docker-compose.yml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"../docker-compose.override.yml"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rails"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspaceFolder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/workspace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"runServices"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"rails"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>vscode</category>
      <category>github</category>
    </item>
    <item>
      <title>Release notes management</title>
      <dc:creator>r7kamura</dc:creator>
      <pubDate>Fri, 12 Aug 2022 02:27:59 +0000</pubDate>
      <link>https://dev.to/r7kamura/release-notes-management-2ho0</link>
      <guid>https://dev.to/r7kamura/release-notes-management-2ho0</guid>
      <description>&lt;p&gt;How do you manage your OSS changelog or release notes?&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%2Fcardnlqg2a8lsx627no3.png" 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%2Fcardnlqg2a8lsx627no3.png" alt="GitHub Releases"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;As for myself, I don't use CHANGELOG file these days and almost exclusively use GitHub Releases.&lt;/p&gt;

&lt;p&gt;There are 3 main reasons for this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Managing hundreds of repositories is getting harder&lt;/li&gt;
&lt;li&gt;GitHub Releases is getting better&lt;/li&gt;
&lt;li&gt;CHANGELOG tends to conflict on large projects&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Release notes
&lt;/h2&gt;

&lt;p&gt;GitHub has the &lt;a href="https://docs.github.com/en//repositories/releasing-projects-on-github/automatically-generated-release-notes" rel="noopener noreferrer"&gt;ability to automatically generate releases notes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This feature summarizes the pull requets that have been merged with the current version. It is quite useful because it includes a link to each pull request, the name of author, and a list of contributors for that version. If you try to generate this by yourself, it's not that easy. Most of all, I like the fact that this feature is officially provided by GitHub.&lt;/p&gt;

&lt;p&gt;If you put &lt;code&gt;.github/release.yml&lt;/code&gt;, it also generates separate sections according to the labels attached to the pull requests. Since I like the &lt;a href="https://keepachangelog.com/en/1.0.0/" rel="noopener noreferrer"&gt;keep-a-changelog&lt;/a&gt;-like format, I have prepared these 6 labels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add&lt;/li&gt;
&lt;li&gt;change&lt;/li&gt;
&lt;li&gt;deprecate&lt;/li&gt;
&lt;li&gt;fix&lt;/li&gt;
&lt;li&gt;remove&lt;/li&gt;
&lt;li&gt;security&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically, I add these labels at the time of review, but even if I forget to do so, I can add them before release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Labels
&lt;/h2&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%2Fhhdlyym1ro8a2v60lz02.png" 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%2Fhhdlyym1ro8a2v60lz02.png" alt="GitHub Labels"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's troublesome to prepare these labels on each repository by hand, so I've automated it by using &lt;a href="https://github.com/r7kamura/github-label-sync-action" rel="noopener noreferrer"&gt;github-label-sync-action&lt;/a&gt;. If you put the name, description, and color of each label in &lt;code&gt;.github/labels.yml&lt;/code&gt;, it will automatically sync them to GitHub.&lt;/p&gt;

&lt;p&gt;Furthermore, I would like to automate the process of preparing this configuration YAML files, so I created &lt;a href="https://github.com/r7kamura/github-keepachangelog-template" rel="noopener noreferrer"&gt;r7kamura/github-keepachangelog-template&lt;/a&gt; for that and use &lt;a href="https://github.com/r7kamura/gitcp" rel="noopener noreferrer"&gt;gitcp&lt;/a&gt; to copy it to the current directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gitcp r7kamura/github-keepachangelog-template
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Workflows
&lt;/h2&gt;

&lt;p&gt;I also use GitHub Actions these days to automate the process of publishing new versions, such as tags and releases. For example, with a npm package, we put the &lt;code&gt;version&lt;/code&gt; in &lt;code&gt;manifest.json&lt;/code&gt;. I have a workflow that detects the version change and automatically creates a new Git tag, a new GitHub Release, and so on.&lt;/p&gt;

&lt;p&gt;Since it is time-consuming to prepare such workflows for each repository, I use &lt;a href="https://docs.github.com/en//actions/using-workflows/reusing-workflows" rel="noopener noreferrer"&gt;Reusable workflows&lt;/a&gt; and organize my own workflows in &lt;a href="https://github.com/r7kamura/workflows" rel="noopener noreferrer"&gt;r7kamura/workflows&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is an example workflow to release my VSCode extension from &lt;a href="https://github.com/r7kamura/vscode-ruby-light" rel="noopener noreferrer"&gt;vscode-ruby-light&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;r7kamura/workflows/.github/workflows/vscode-extension-release.yml@main&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;vsce-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.VSCE_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just write this, change the version field in &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;git push&lt;/code&gt; it, and it will automatically create a new git tag, a new GitHub Release with automatically generated release notes, build the extension, and publish it to the VSCode marketplace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I wrote about how I manage my release notes.&lt;/p&gt;

&lt;p&gt;Release notes are very important source of information for users. However, I understand that it can be difficult to maintain them. I would like to be able to automate it and manage it without incurring costs.&lt;/p&gt;

</description>
      <category>github</category>
      <category>changelog</category>
    </item>
    <item>
      <title>BUNDLE_ONLY is now available</title>
      <dc:creator>r7kamura</dc:creator>
      <pubDate>Tue, 09 Aug 2022 22:05:53 +0000</pubDate>
      <link>https://dev.to/r7kamura/bundleonly-is-now-available-2bb7</link>
      <guid>https://dev.to/r7kamura/bundleonly-is-now-available-2bb7</guid>
      <description>&lt;p&gt;I recently added &lt;code&gt;BUNDLE_ONLY&lt;/code&gt; option to &lt;a href="https://bundler.io/"&gt;Bundler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is the long-awaited feature from 8+ years ago that allows you to install only the gem groups you need, available from bundler 2.3.19.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rubygems/bundler-features/issues/59"&gt;https://github.com/rubygems/bundler-features/issues/59&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rubygems/bundler/pull/4907"&gt;https://github.com/rubygems/bundler/pull/4907&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rubygems/rubygems/issues/4048"&gt;https://github.com/rubygems/rubygems/issues/4048&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rubygems/rubygems/pull/5759"&gt;https://github.com/rubygems/rubygems/pull/5759&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, in our Rails app, we have a crazy number of gem groups (terrible I know). There is a workflow to run RuboCop with GitHub Actions, but this workflow only requires the gems in the rubocop gem group to be installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; .github/workflows/rubocop.yml
 env:
&lt;span class="gd"&gt;-  BUNDLE_WITHOUT: &amp;gt;
-    danger
-    default
-    development
-    feature1
-    feature2
-    feature3
-    feature4
-    feature5
-    feature6
-    feature7
-    mobile
-    production
-    qa
-    staging1
-    staging2
-    staging3
-    test
&lt;/span&gt;&lt;span class="gi"&gt;+  BUNDLE_ONLY: rubocop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Previously, it was necessary to use &lt;code&gt;BUNDLE_WITHOUT&lt;/code&gt; to exclude unwanted gem groups one by one, but now &lt;code&gt;BUNDLE_ONLY&lt;/code&gt; allows us to write this way 😃&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Run YARD as RSpec</title>
      <dc:creator>r7kamura</dc:creator>
      <pubDate>Thu, 04 Aug 2022 01:45:51 +0000</pubDate>
      <link>https://dev.to/r7kamura/run-yard-as-rspec-3hnn</link>
      <guid>https://dev.to/r7kamura/run-yard-as-rspec-3hnn</guid>
      <description>&lt;p&gt;Let me introduce &lt;a href="https://github.com/r7kamura/yardspec"&gt;yardspec&lt;/a&gt; in this article.&lt;/p&gt;

&lt;p&gt;yardspec is a Ruby gem that supports executing YARD documentation &lt;code&gt;@example&lt;/code&gt;s as RSpec examples.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Foo&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Bar&lt;/span&gt;
    &lt;span class="c1"&gt;# @example returns "baz"&lt;/span&gt;
    &lt;span class="c1"&gt;#   expect(Foo::Bar.new.baz).to eq('baz')&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;
    &lt;span class="c1"&gt;# @example returns "bazbaz" for count 2&lt;/span&gt;
    &lt;span class="c1"&gt;#   expect(Foo::Bar.new.baz(count: 2)).to eq('bazbaz')&lt;/span&gt;
    &lt;span class="c1"&gt;#&lt;/span&gt;
    &lt;span class="c1"&gt;# @return [String]&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="s1"&gt;'baz'&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It interprets the documentation examples above as the following RSpec examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'Foo::Bar#baz'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'returns "baz"'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'baz'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'returns "bazbaz" for count 2'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;count: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bazbaz'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and running RSpec as follows will automatically test them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rspec

Foo::Bar#baz
  returns "baz"
  returns "bazbaz" for count 2

Finished in 0.00129 seconds (files took 0.087 seconds to load)
2 examples, 0 failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you may have noticed, this is the doctest concept from Rust, Python, Elixir, etc, dropped into RSpec and YARD for Ruby.&lt;/p&gt;

&lt;p&gt;It is often helpful to include example code in your documentation and demonstrate the results of executing them. But it is important to ensure that the documentation stays up-to-date with the code.&lt;/p&gt;

&lt;p&gt;That is the purpose for which yardspec was created. By writing example code that actually works, users can read it with confidence and try it out for themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/r7kamura/yardspec"&gt;https://github.com/r7kamura/yardspec&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;yardspec source code&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.rubydoc.info/gems/yard/file/docs/Tags.md#example"&gt;&lt;/a&gt;&lt;a href="https://www.rubydoc.info/gems/yard/file/docs/Tags.md#example"&gt;https://www.rubydoc.info/gems/yard/file/docs/Tags.md#example&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;YARD &lt;code&gt;@example&lt;/code&gt; tag&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html"&gt;&lt;/a&gt;&lt;a href="https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html"&gt;https://doc.rust-lang.org/rustdoc/write-documentation/documentation-tests.html&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Rust doctest&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rspec</category>
      <category>yard</category>
    </item>
    <item>
      <title>Automate RuboCop ToDo fix</title>
      <dc:creator>r7kamura</dc:creator>
      <pubDate>Sat, 30 Jul 2022 09:26:25 +0000</pubDate>
      <link>https://dev.to/r7kamura/automate-refactoring-by-rubocop-todo-corrector-2mli</link>
      <guid>https://dev.to/r7kamura/automate-refactoring-by-rubocop-todo-corrector-2mli</guid>
      <description>&lt;p&gt;Let me introduce &lt;a href="https://github.com/r7kamura/rubocop-todo-corrector" rel="noopener noreferrer"&gt;rubocop-todo-corrector&lt;/a&gt; in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  ToDo list based linting
&lt;/h2&gt;

&lt;p&gt;These days, many projects are using Lint tools to self-diagnose their code.&lt;/p&gt;

&lt;p&gt;e.g.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESLint for JavaScript&lt;/li&gt;
&lt;li&gt;rustfmt for Rust&lt;/li&gt;
&lt;li&gt;RuboCop for Ruby&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RuboCop has the ability to detect existing offenses and automatically generate a file named &lt;code&gt;.rubocop_todo.yml&lt;/code&gt;. This file acts as a to-do list, so we can gradually fix the offenses.&lt;/p&gt;

&lt;p&gt;This mechanism is very powerful when introducing a new Lint tool in the middle of an existing project, or when introducing new rules by upgrading the existing Lint tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate it
&lt;/h2&gt;

&lt;p&gt;The improvement process cycle would look like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select one lint rule from ToDo list&lt;/li&gt;
&lt;li&gt;Autocorrect existing offenses (or disable the rule)&lt;/li&gt;
&lt;li&gt;Re-generate ToDo&lt;/li&gt;
&lt;li&gt;Create a Pull Request&lt;/li&gt;
&lt;li&gt;Review and merge the Pull Request&lt;/li&gt;
&lt;li&gt;Repeat from 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, it's difficult to do this manually. So, let's automate with &lt;a href="https://github.com/r7kamura/rubocop-todo-corrector" rel="noopener noreferrer"&gt;rubocop-todo-corrector&lt;/a&gt; custom GitHub action.&lt;/p&gt;

&lt;h2&gt;
  
  
  rubocop-todo-corrector
&lt;/h2&gt;

&lt;p&gt;It's easy to use, just place the following YAML file in your repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/rubocop-todo-corrector.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rubocop-todo-corrector&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;closed&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;cop_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pass cop name if you want to pick a specific cop.&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
      &lt;span class="na"&gt;ignore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check this with cop_name if you want to ignore a specific cop.&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;boolean&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;r7kamura/rubocop-todo-corrector@v0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ignore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.ignore }}&lt;/span&gt;
          &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rubocop-todo-corrector&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then create "rubocop-todo-corrector" label on your repository.&lt;/p&gt;

&lt;p&gt;Now you can run this workflow by "Run workflow" button on actions page:&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%2Fhq74x4rs1w8frpz3xhf8.png" 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%2Fhq74x4rs1w8frpz3xhf8.png" alt="actions page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the workflow is complete, a pull request is created as follows:&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%2Fztq4p5am60za0biwqp4b.png" 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%2Fztq4p5am60za0biwqp4b.png" alt="pull request"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Review this pull request, and if it looks good, press the Merge button.&lt;/p&gt;

&lt;p&gt;The next pull request will then be automatically created. That is, all we have to do is to press the Merge button, and the code automatically gets refactored more and more. What an easy job!&lt;/p&gt;

&lt;h2&gt;
  
  
  Our story
&lt;/h2&gt;

&lt;p&gt;In our huge Rails app, which has been running for about 10 years, we introduced RuboCop in the middle of the process, and at first there were 200,000 offenses.&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%2F37165rqvfo505oh497cm.png" 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%2F37165rqvfo505oh497cm.png" alt="chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(I also described how to make this chart at &lt;a href="https://dev.to/r7kamura/aggregate-offenses-count-in-rubocoptodoyml-4oda"&gt;Aggregate offenses count in .rubocop_todo.yml - DEV Community&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;There were so many offenses that we almost gave up on fixing them, but recently we introduced rubocop-todo-corrector and the offenses slowly started decreasing.&lt;/p&gt;

&lt;p&gt;Since its introduction about 2 months ago, we have been able to correct about 40,000+ offenses. The developers are getting used to the review &amp;amp; merge process, which is gradually accelerating, and it looks like we will be able to reduce offenses to almost zero in another year or so.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;In this article, I introduced the background and usage of a custom GitHub action called &lt;a href="https://github.com/r7kamura/rubocop-todo-corrector" rel="noopener noreferrer"&gt;rubocop-todo-corrector&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There are other features that were not covered in this article, such as automatic reviewer assignment and disabling as well as fixing violations, so if you are interested please read the README.&lt;/p&gt;

&lt;p&gt;Automating the refactoring process is a powerful mechanism. Let's automate more and more and keep the good code quality.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rubocop</category>
      <category>refactoring</category>
    </item>
    <item>
      <title>Aggregate RuboCop ToDo count</title>
      <dc:creator>r7kamura</dc:creator>
      <pubDate>Tue, 26 Jul 2022 00:16:14 +0000</pubDate>
      <link>https://dev.to/r7kamura/aggregate-offenses-count-in-rubocoptodoyml-4oda</link>
      <guid>https://dev.to/r7kamura/aggregate-offenses-count-in-rubocoptodoyml-4oda</guid>
      <description>&lt;p&gt;Are you all fighting RuboCop offenses?&lt;/p&gt;

&lt;p&gt;Today I have written a simple Ruby script to aggregate the number of offenses in .rubocop_todo.yml, so let me share it in this article.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# aggregate_rubocop_todo_offenses.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'date'&lt;/span&gt;

&lt;span class="c1"&gt;# Aggregate up to 365 days in advance.&lt;/span&gt;
&lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

  &lt;span class="c1"&gt;# Get the 1st commit hash before `i` days.&lt;/span&gt;
  &lt;span class="c1"&gt;# The format `%H` means "commit hash". See `git log --help` for more info.&lt;/span&gt;
  &lt;span class="n"&gt;commit_sha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`git log -1 --format='%H' --before=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.day`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;

  &lt;span class="c1"&gt;# Get the .rubocop_todo.yml content at the commit.&lt;/span&gt;
  &lt;span class="n"&gt;rubocop_todo_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sb"&gt;`git show &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;commit_sha&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:.rubocop_todo.yml`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;

  &lt;span class="c1"&gt;# Get the total count of offenses in the .rubocop_todo.yml.&lt;/span&gt;
  &lt;span class="c1"&gt;# The offenses count is described in the form like `# Offense count: 42` per cop.&lt;/span&gt;
  &lt;span class="n"&gt;offenses_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rubocop_todo_content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/count: (\d+)/&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="ss"&gt;:to_i&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;

  &lt;span class="c1"&gt;# Output date and count in TSV format.&lt;/span&gt;
  &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="no"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;offenses_count&lt;/span&gt;
  &lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the above script and you will see the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ruby aggregate_rubocop_todo_offenses.rb
2022-07-26      164761
2022-07-25      164763
2022-07-24      164764
2022-07-23      165303
2022-07-22      165303
2022-07-21      165296
2022-07-20      165290
...
2021-11-27      193170
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In such cases, it's convenient to paste the data into Google Spreadsheet to generate a chart.&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%2F3bdlnfqctsx22e0rnnbz.png" 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%2F3bdlnfqctsx22e0rnnbz.png" alt="chart"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a crazy amount, but slowly decreasing in these days. Good...&lt;/p&gt;

&lt;p&gt;In fact, I recently created a GitHub Action workflow that automatically corrects offenses and continuously creates new pull requests, so offenses count is gradually decreasing only by pushing merge button, but I'll get to that in another time.&lt;/p&gt;

&lt;p&gt;If you are interested, please add your reactions! 🦄 ✨&lt;/p&gt;

</description>
      <category>rubocop</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Custom cop in 30 min</title>
      <dc:creator>r7kamura</dc:creator>
      <pubDate>Thu, 21 Jul 2022 01:36:54 +0000</pubDate>
      <link>https://dev.to/r7kamura/custom-ruby-cop-in-30-min-5ao8</link>
      <guid>https://dev.to/r7kamura/custom-ruby-cop-in-30-min-5ao8</guid>
      <description>&lt;p&gt;This article is the detailed text version of this video:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=HTuNoq9aEWQ&amp;amp;t=1298s&amp;amp;ab_channel=r7kamura"&gt;Custom cop in 30 min - YouTube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll explain how to create a custom Ruby lint rule with RuboCop (a.k.a. "custom cop") from scratch. In this tutorial, I'll write an example cop to sort Hash literal elements by their keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start from template
&lt;/h2&gt;

&lt;p&gt;The cop to be created in this tutorial will be implemented in the form of a RuboCop plugin. Because RuboCop does not yet have a plug-in mechanism, we have to write a lot of additional code to make it work as a plugin.&lt;/p&gt;

&lt;p&gt;This will be a bit hard work, so I prepared a template that includes the necessary code for scaffolding that.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/r7kamura/rubocop-extension-template"&gt;https://github.com/r7kamura/rubocop-extension-template&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;To add a new cop, you need to do the following steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add test

&lt;ul&gt;
&lt;li&gt;spec/rubocop/cop/my_extension/hash_literal_order_spec.rb&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Add cop class

&lt;ul&gt;
&lt;li&gt;lib/rubocop/cop/my_extension/hash_literal_order.rb&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Load cop class

&lt;ul&gt;
&lt;li&gt;lib/my_extension.rb&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Add default config

&lt;ul&gt;
&lt;li&gt; default.yml&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RuboCop itself and plugins like rubocop-rspec and rubocop-rails are also implemented in the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add test
&lt;/h2&gt;

&lt;p&gt;Basically, cop tests are written in RSpec. You don't need to have written RSpec before. Just copy and paste the existing sample code and it will work.&lt;/p&gt;

&lt;p&gt;All you need to know is that these 3 methods are officialy provided by RuboCop.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;expect_offense(code_with_message)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expect_no_offenses(code)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expect_correction(code)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;MyExtension/HashLiteralOrder&lt;/code&gt; is the cop name that we are writing.&lt;br&gt;
Every cop has a corresponding class, and they are defined under &lt;code&gt;::RuboCop::Cop&lt;/code&gt; namespace.&lt;br&gt;
This is apparently no exception, even for 3rd party plug-ins.&lt;br&gt;
So the minimum example of our test code will be like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# spec/rubocop/cop/my_extension/hash_literal_order_spec.rb&lt;/span&gt;
&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt; &lt;span class="no"&gt;RuboCop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Cop&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;MyExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HashLiteralOrder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:config&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'autocorrects offense'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;expect_offense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
      { b: 1, c: 1, a: 1 }
      ^^^^^^^^^^^^^^^^^^^^ Sort Hash literal entries by key.
&lt;/span&gt;&lt;span class="no"&gt;    TEXT&lt;/span&gt;

    &lt;span class="n"&gt;expect_correction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;RUBY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
      { a: 1, b: 1, c: 1 }
&lt;/span&gt;&lt;span class="no"&gt;    RUBY&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;expect_offense&lt;/code&gt; takes as its argument not only the Ruby code, but also a string with the offence message. This is a bit tricky, but it actually useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add cop class
&lt;/h2&gt;

&lt;p&gt;Once you have written the tests, it's time to write the implementation.&lt;/p&gt;

&lt;p&gt;I'll write the near-complete implementation of the cop.&lt;br&gt;
I know there are a lot of parts you don't know, but don't panic.&lt;/p&gt;

&lt;p&gt;From here on, I'll explain step by step in comments in the code,&lt;br&gt;
not in plain text, so please read along with the code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/rubocop/cop/my_extension/hash_literal_order.rb&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;RuboCop&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Cop&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;MyExtension&lt;/span&gt;
      &lt;span class="c1"&gt;# By convention, we write fairly detailed documentation for each cop&lt;/span&gt;
      &lt;span class="c1"&gt;# using YARD comments. Even official RuboCop cops do so, and the official&lt;/span&gt;
      &lt;span class="c1"&gt;# documentation site is automatically generated from these comments there.&lt;/span&gt;
      &lt;span class="c1"&gt;# e.g. https://docs.rubocop.org/rubocop/cops_layout.html&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;# Sort Hash literal entries by key.&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;# @example&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;#   # bad&lt;/span&gt;
      &lt;span class="c1"&gt;#   {&lt;/span&gt;
      &lt;span class="c1"&gt;#     b: 1,&lt;/span&gt;
      &lt;span class="c1"&gt;#     a: 1,&lt;/span&gt;
      &lt;span class="c1"&gt;#     c: 1&lt;/span&gt;
      &lt;span class="c1"&gt;#   }&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;#   # good&lt;/span&gt;
      &lt;span class="c1"&gt;#   {&lt;/span&gt;
      &lt;span class="c1"&gt;#     a: 1,&lt;/span&gt;
      &lt;span class="c1"&gt;#     b: 1,&lt;/span&gt;
      &lt;span class="c1"&gt;#     c: 1&lt;/span&gt;
      &lt;span class="c1"&gt;#   }&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;#&lt;/span&gt;
      &lt;span class="c1"&gt;# As I mentioned previously, if you want to create `MyExtenison/HashLiteralOrder` cop,&lt;/span&gt;
      &lt;span class="c1"&gt;# the class name should be `::RuboCop::Cop::MyExtension::HashLiteralOrder`.&lt;/span&gt;
      &lt;span class="c1"&gt;# All cop classes must inherit from RuboCop::Cop::Base,&lt;/span&gt;
      &lt;span class="c1"&gt;# so here we wrote `&amp;lt; Base` as well.&lt;/span&gt;
      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HashLiteralOrder&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;

        &lt;span class="c1"&gt;# To support `--autocorrect` in this cop,&lt;/span&gt;
        &lt;span class="c1"&gt;# we need to extend this module.&lt;/span&gt;
        &lt;span class="kp"&gt;extend&lt;/span&gt; &lt;span class="no"&gt;AutoCorrector&lt;/span&gt;

        &lt;span class="c1"&gt;# If you define `MSG` constant in cop class,&lt;/span&gt;
        &lt;span class="c1"&gt;# RuboCop will use it as an offense message for this cop.&lt;/span&gt;
        &lt;span class="no"&gt;MSG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Sort Hash literal entries by key.'&lt;/span&gt;

        &lt;span class="c1"&gt;# RuboCop provides `def_node_matcher` to easily define pattern-matching method,&lt;/span&gt;
        &lt;span class="c1"&gt;# which takes a Symbol method name and a String AST pattern,&lt;/span&gt;
        &lt;span class="c1"&gt;# In this case, it defines `#hash_literal_order` method,&lt;/span&gt;
        &lt;span class="c1"&gt;# which takes an AST node as argument and returns a boolean value.&lt;/span&gt;
        &lt;span class="c1"&gt;#&lt;/span&gt;
        &lt;span class="c1"&gt;# For example, this method returns true for `{ a: 1, b: 2 }`,&lt;/span&gt;
        &lt;span class="c1"&gt;# and returns false for `[1, 2]` and `{ a =&amp;gt; b }`.&lt;/span&gt;
        &lt;span class="c1"&gt;#&lt;/span&gt;
        &lt;span class="c1"&gt;# @!method hash_literal?(node)&lt;/span&gt;
        &lt;span class="c1"&gt;# @param [RuboCop::AST::HashNode] node&lt;/span&gt;
        &lt;span class="c1"&gt;# @return [Boolean]&lt;/span&gt;
        &lt;span class="n"&gt;def_node_matcher&lt;/span&gt; &lt;span class="ss"&gt;:hash_literal?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;PATTERN&lt;/span&gt;&lt;span class="sh"&gt;
          (hash
            (pair
              {sym | str}
              _
            )+
          )
&lt;/span&gt;&lt;span class="no"&gt;        PATTERN&lt;/span&gt;

        &lt;span class="c1"&gt;# This is a callback method that is called when RuboCop detects&lt;/span&gt;
        &lt;span class="c1"&gt;# a Hash node. We use this mechanism to implement the registration&lt;/span&gt;
        &lt;span class="c1"&gt;# and autocorrection of offenses when we find a code pattern that&lt;/span&gt;
        &lt;span class="c1"&gt;# we want to detect.&lt;/span&gt;
        &lt;span class="c1"&gt;#&lt;/span&gt;
        &lt;span class="c1"&gt;# The key method is `add_offense`.&lt;/span&gt;
        &lt;span class="c1"&gt;# By calling this, you can tell RuboCop that you've found an offense.&lt;/span&gt;
        &lt;span class="c1"&gt;#&lt;/span&gt;
        &lt;span class="c1"&gt;# In this case, if the keys of the target Hash node are all Symbols&lt;/span&gt;
        &lt;span class="c1"&gt;# or Strings and not sorted by name, we consider it an offense,&lt;/span&gt;
        &lt;span class="c1"&gt;# then replace the entire Hash node with a new code, which is&lt;/span&gt;
        &lt;span class="c1"&gt;# specified by the Ruby code as a String.&lt;/span&gt;
        &lt;span class="c1"&gt;#&lt;/span&gt;
        &lt;span class="c1"&gt;# @param [RuboCop::AST::HashNode] node&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;hash_literal?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sorted?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

          &lt;span class="n"&gt;add_offense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;corrector&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;corrector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;autocorrect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&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;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="kp"&gt;private&lt;/span&gt;

        &lt;span class="c1"&gt;# ... some internal implementation ...&lt;/span&gt;
        &lt;span class="c1"&gt;# ... such as `#sorted?(node)`,&lt;/span&gt;
        &lt;span class="c1"&gt;# ... `#autocorrect(node)`, and so on ...&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The details are not described due to space limitations, so please read the original source code here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/r7kamura/sevencop/blob/main/lib/rubocop/cop/sevencop/hash_literal_order.rb"&gt;https://github.com/r7kamura/sevencop/blob/main/lib/rubocop/cop/sevencop/hash_literal_order.rb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AST pattern matching
&lt;/h3&gt;

&lt;p&gt;The syntax of this pattern match has a rather strange style and may be difficult to understand at first sight. There is an easy-to-understand explanation at &lt;a href="https://docs.rubocop.org/rubocop-ast/node_pattern.html"&gt;Node Pattern :: RuboCop Docs&lt;/a&gt;, so it would be better to write it by hand while looking at this at first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(hash
  (pair
    {sym | str}
    _
  )+
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a useful CLI tool in the parser gem called &lt;code&gt;ruby-parse&lt;/code&gt;, which shows the parsed results as ASTs, and I often check it when writing Cop like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;parser
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{ a: 1, b: 1 }'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; example.rb
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ruby-parse example.rb
&lt;span class="go"&gt;(hash
  (pair
    (sym :a)
    (int 1))
  (pair
    (sym :b)
    (int 1)))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Load cop class
&lt;/h2&gt;

&lt;p&gt;Don't forget to load the cop class file.&lt;br&gt;
Sometimes I forget this and get confused.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# lib/my_extension.rb&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rubocop/cop/my_extension/hash_literal_order'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add default config
&lt;/h2&gt;

&lt;p&gt;Finally, don't forget to add default config for the cop you added.&lt;/p&gt;

&lt;p&gt;Each cop can have its own configuration values, and it is common practice for RuboCop plug-ins to include their default settings in &lt;code&gt;default.yml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# default.yml&lt;/span&gt;
&lt;span class="na"&gt;MyExtension/HashLiteralOrder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;Sort Hash literal entries by key.&lt;/span&gt;
  &lt;span class="na"&gt;Enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;VersionAdded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not the cop is complete! This cop works as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ bundle exec rubocop
Inspecting 1 files
C

Offenses:

example.rb:3:1: C: [Correctable] MyExtension/HashLiteralOrder: Sort Hash literal entries by key.
{ b: 2, a: 1, c: 3 }
^^^^^^^^^^^^^^^^^^^^

1 files inspected, 1 offense detected, 1 offense autocorrectable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same cop we created here is included in my gem called &lt;a href="https://github.com/r7kamura/sevencop"&gt;sevencop&lt;/a&gt;. You can try this cop in the following repository.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/r7kamura/sevencop-hash-literal-order-example"&gt;https://github.com/r7kamura/sevencop-hash-literal-order-example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I explained how to create a custom cop from scratch in this tutorial.&lt;/p&gt;

&lt;p&gt;The finished version of this cop's source is available at GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/r7kamura/sevencop/blob/main/lib/rubocop/cop/sevencop/hash_literal_order.rb"&gt;https://github.com/r7kamura/sevencop/blob/main/lib/rubocop/cop/sevencop/hash_literal_order.rb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As mentioned at the beginning of this article, I'm also providing a live coding video for this tutorial. If you have any questions, don't hesitate to ask, either in the comments of this article or the video.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=HTuNoq9aEWQ&amp;amp;ab_channel=r7kamura"&gt;Custom cop in 30 min - YouTube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to know more about how to create custom cops, I recommend that you read these pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rubocop/rubocop"&gt;https://github.com/rubocop/rubocop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rubocop/rubocop-ast"&gt;https://github.com/rubocop/rubocop-ast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/whitequark/parser"&gt;https://github.com/whitequark/parser&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I have posted other tutorials if you would like to see them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/r7kamura/chrome-extension-in-20-minutes-47ej"&gt;Chrome extension in 20 min - DEV Community 👩‍💻👨‍💻&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/c/r7kamura"&gt;r7kamura - YouTube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading so far. If you have any questions, please ask anything in the comments.&lt;/p&gt;

&lt;p&gt;Good coding!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>ruby</category>
      <category>rubocop</category>
    </item>
    <item>
      <title>Chrome extension in 20 min</title>
      <dc:creator>r7kamura</dc:creator>
      <pubDate>Tue, 19 Jul 2022 05:42:48 +0000</pubDate>
      <link>https://dev.to/r7kamura/chrome-extension-in-20-minutes-47ej</link>
      <guid>https://dev.to/r7kamura/chrome-extension-in-20-minutes-47ej</guid>
      <description>&lt;p&gt;This article is the detailed text version of the video I recently posted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=B5wdRcv-zQA" rel="noopener noreferrer"&gt;Chrome extension in 20 min - YouTube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'll explain how to create a Chrome extension from scratch that turns a new tab into a notepad.&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%2Fwj7p5vef7bcl4nd5gmde.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%2Fwj7p5vef7bcl4nd5gmde.gif" alt="demo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I use the following tools in this tutorial, but you don't have to have used these tools before.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;node.js (14.x or later)&lt;/li&gt;
&lt;li&gt;typescript&lt;/li&gt;
&lt;li&gt;css&lt;/li&gt;
&lt;li&gt;vite&lt;/li&gt;
&lt;li&gt;monaco-editor&lt;/li&gt;
&lt;li&gt;types/chrome&lt;/li&gt;
&lt;li&gt;crxjs/vite-plugin&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create a new vite project
&lt;/h2&gt;

&lt;p&gt;First, create a new vite project by &lt;code&gt;create-vite&lt;/code&gt; npm package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init vite@2.9.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npm init x&lt;/code&gt; (or &lt;code&gt;npm create x&lt;/code&gt; as an alias) runs &lt;code&gt;npm exec create-x&lt;/code&gt;, so in this case you will be running &lt;code&gt;npm exec create-vite&lt;/code&gt;. See &lt;code&gt;npm help init&lt;/code&gt; for more detials.&lt;/p&gt;

&lt;p&gt;As you may have noticed, I specified the version &lt;code&gt;2.9.0&lt;/code&gt;. This is because the current latest vite version 3.0.0 doesn't work well with &lt;code&gt;crxjs/vite-plugin&lt;/code&gt; yet, so we need to choose the older version for now.&lt;/p&gt;

&lt;p&gt;After running the command, you are ready to build the default HTML + CSS + JavaScript source.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will continuously compile the source files and output files into ./dist/ directory, then start &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; as a preview HTTP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an empty Chrome extension
&lt;/h2&gt;

&lt;p&gt;To create an empty Chrome extension, you need to write manifest.json.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"newtab-editor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Editor in new tab"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"manifest_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, only putting this file won't cause Vite to include manifest.json to build target.&lt;/p&gt;

&lt;p&gt;To achieve this, you also need to write vite.config.ts.&lt;br&gt;
The &lt;code&gt;@crxjs/vite-plugin&lt;/code&gt; does all the tedious work, so all you have to do is to pass manifest.json to it.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;crx&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@crxjs/vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;manifest&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/manifest.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;crx&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;manifest&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;To import crx, don't forget to install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev @crxjs/vite-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After these steps, you can build the extension.&lt;br&gt;
If you are still leaving &lt;code&gt;npm run dev&lt;/code&gt; on, you should have a built Chrome extension in the ./dist/ directory.&lt;/p&gt;
&lt;h2&gt;
  
  
  Install the extension
&lt;/h2&gt;

&lt;p&gt;To install the extension from local files, follow the steps below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;open Google Chrome&lt;/li&gt;
&lt;li&gt;go to Chrome extensions page (chrome://extensions/)&lt;/li&gt;
&lt;li&gt;enable "Developer mode"&lt;/li&gt;
&lt;li&gt;click "Load unpacked extension"&lt;/li&gt;
&lt;li&gt;select the ./dist/ directory&lt;/li&gt;
&lt;/ol&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%2Fs9t3kfvezd96lshkfhxb.png" 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%2Fs9t3kfvezd96lshkfhxb.png" alt="chrome extensions page"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Override new tab
&lt;/h2&gt;

&lt;p&gt;To override new tab page by our extension, add &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides" rel="noopener noreferrer"&gt;chrome_url_overrides&lt;/a&gt; property to manifest.json.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"chrome_url_overrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"newtab"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.html"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reloading the extension, you should see the index.html in the new tab.&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%2F9ha8t8dx6btnkcupluwo.png" 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%2F9ha8t8dx6btnkcupluwo.png" alt="index.html in new tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add monaco-editor
&lt;/h2&gt;

&lt;p&gt;This time I will use an editor called &lt;a href="https://microsoft.github.io/monaco-editor/" rel="noopener noreferrer"&gt;monaco-editor&lt;/a&gt;. This is a Visual Studio Code-based editor, which allows you to achieve roughly the same functionality in your browser.&lt;/p&gt;

&lt;p&gt;It is easy to use, just import and mount it on some HTML element and it works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save monaco-editor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/main.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./style.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;monaco&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;monaco-editor&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLDivElement&lt;/span&gt;&lt;span class="o"&gt;&amp;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;#app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;monaco&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;markdown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;minimap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vs-dark&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reloading the extension, you should see the editor in the new tab.&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%2Fuc25odt1shicfbrkrdal.png" 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%2Fuc25odt1shicfbrkrdal.png" alt="monaco-editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Save content via storage API
&lt;/h2&gt;

&lt;p&gt;To save editor content, we can use the &lt;a href="https://developer.chrome.com/docs/extensions/reference/storage/" rel="noopener noreferrer"&gt;storage API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, however, it is not possible to call these APIs directly from index.html.&lt;br&gt;
Instead, you need to have a background process to which these APIs are allowed and send requests to it.&lt;/p&gt;

&lt;p&gt;First, the editor content is changed, send a request to the background process to save the content.&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="c1"&gt;// src/main.ts&lt;/span&gt;
&lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDidChangeModelContent&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&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;saveContent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&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;Second, when you opened the new tab, restore the content you saved.&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="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendMessage&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;loadContent&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;content&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="nx"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&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, prepare a backgruond process that accepts requests.&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="c1"&gt;// src/background.ts&lt;/span&gt;
&lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addListener&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sendResponse&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;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;loadContent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&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;content&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="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;content&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="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;saveContent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;break&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's done!&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%2Fwj7p5vef7bcl4nd5gmde.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%2Fwj7p5vef7bcl4nd5gmde.gif" alt="demo"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;In this tutorial, I explained how to create a Chrome extension from scratch that turns a new tab into a notepad.&lt;/p&gt;

&lt;p&gt;The finished version of this Chrome extension is available on Chrome Web Store.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://chrome.google.com/webstore/detail/editabro/eodgdnjgkjjlohklhoaapfhghgcoihmf" rel="noopener noreferrer"&gt;Editabro - Chrome Web Store&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, the source code is also available on GitHub. If you are interested, I'd recommend you to read the source code. It includes techniques such as &lt;code&gt;debounce&lt;/code&gt; to reduce the amount of communication with background processes, removing unnecessary language functions to reduce bundled code size, and using Worker to improve performance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/r7kamura/editabro" rel="noopener noreferrer"&gt;https://github.com/r7kamura/editabro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I learned how to create Chrome extensions these days from &lt;a class="mentioned-user" href="https://dev.to/jacksteamdev"&gt;@jacksteamdev&lt;/a&gt;'s excellent article series. I would like to take this opportunity to thank you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/jacksteamdev/create-a-vite-react-chrome-extension-in-90-seconds-3df7"&gt;Create a Vite-React Chrome Extension in 90 seconds - DEV Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As noted at the beginning of this article, I'm also providing a live coding video version of this tutorial. If you have any questions, don't hesitate to ask, either in the comments of this article or in the comments of the video.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=B5wdRcv-zQA" rel="noopener noreferrer"&gt;Chrome extension in 20 min - YouTube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will be posting several other coding videos, so if you are interested, please check out my channel.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/c/r7kamura" rel="noopener noreferrer"&gt;https://www.youtube.com/c/r7kamura&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading this far. If you have any questions, please ask anything in the comments.&lt;/p&gt;

&lt;p&gt;Good coding!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>javascript</category>
      <category>chrome</category>
      <category>extension</category>
    </item>
  </channel>
</rss>
