<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Blog — George Mishurovsky: posts tagged code style</title>
<link>https://mishurovsky.com/blog/?go=tags/code-style/</link>
<description>A blog by George Mishurovsky — a senior software engineer with a medical degree. Drawing from both engineering and scientific thinking, he explores software, architecture, design, psychology, and product thinking.</description>
<author></author>
<language>en</language>
<generator>Aegea 11.3 (v4134e)</generator>

<itunes:owner>
<itunes:name></itunes:name>
<itunes:email>george@mishurovsky.com</itunes:email>
</itunes:owner>
<itunes:subtitle>A blog by George Mishurovsky — a senior software engineer with a medical degree. Drawing from both engineering and scientific thinking, he explores software, architecture, design, psychology, and product thinking.</itunes:subtitle>
<itunes:image href="https://mishurovsky.com/blog/pictures/userpic/userpic-square@2x.jpg?1753619610" />
<itunes:explicit>no</itunes:explicit>

<item>
<title>Enforce Any Code Style Constraint with ESLint</title>
<guid isPermaLink="false">7</guid>
<link>https://mishurovsky.com/blog/?go=all/enforce-any-code-style-constraint-with-eslint/</link>
<pubDate>Wed, 13 Aug 2025 00:06:32 +0200</pubDate>
<author></author>
<comments>https://mishurovsky.com/blog/?go=all/enforce-any-code-style-constraint-with-eslint/</comments>
<description>
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://mishurovsky.com/blog/pictures/custom-eslint-rule@2x.jpg" width="872" height="577" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;When managing big software projects, it is important to configure code rules for loads of scenarios. Usually, you start with basics, like prohibiting unused variables, requiring &lt;span class="inline-code"&gt;super()&lt;/span&gt; calls in subclass constructors, or catching duplicate conditions in if-else blocks.&lt;/p&gt;
&lt;p&gt;In JavaScript / TypeScript projects that is handled by standard ESLint plugins: @eslint/js, typescript-eslint, eslint-plugin-react, etc. They are more or less easy to configure (ignoring the new flat config which adds the fun of guessing which plugins support it and which don’t yet), and this is where most tech leads stop.&lt;/p&gt;
&lt;p&gt;However, the bigger the project, the more dependencies and opinionated patterns of doing something it accumulates. Large packages may have separate ESLint plugins maintained by independent contributors, but sometimes you’ll want to enforce a rule that doesn’t exist in any plugin. This becomes very important when you expect many people to work on the project, or if you want to use AI agents to write acceptable production code without thorough manual review.&lt;/p&gt;
&lt;p&gt;The good news: &lt;b&gt;ESLint lets you create almost any rule you can imagine!&lt;/b&gt; You don’t need to know specifics of ESLint scripting, since ChatGPT successfully manages to write 95% of the logic, and the remaining 5% would be easy finish when you see selector structure and regex patterns.&lt;/p&gt;
&lt;p&gt;Here, I want to share a specific example. On my current frontend project we use Typescript, React, and Next.js with zustand for client-side state storage. To persist state after a page reload, I added the &lt;span class="inline-code"&gt;persist&lt;/span&gt; plugin that writes and reads data from browser’s &lt;span class="inline-code"&gt;localStorage&lt;/span&gt;. The problem is, Next.js renders client-side components twice: first time on server, then in browser, and &lt;b&gt;both renders must match&lt;/b&gt;. However, the server doesn’t have access to client data, and the store state differs between environments, unless it was not used before.&lt;/p&gt;
&lt;p&gt;The fix is to run rendering with an empty store, and then reading the state specifically on client side. In this case, both server and browser use the same empty state during the first render. This is achieved by using a custom hook, &lt;span class="inline-code"&gt;useStore&lt;/span&gt;, which returns an empty initial state and loads the actual state on client inside a &lt;span class="inline-code"&gt;useEffect&lt;/span&gt;:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="typescript"&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;TypeScript&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-typescript"&gt;export function useStore&amp;lt;T, F&amp;gt;(
  store: (callback: (state: T) =&amp;gt; unknown) =&amp;gt; unknown,
  callback: (state: T) =&amp;gt; F,
) {
  const result = store(callback) as F;
  const [data, setData] = useState&amp;lt;F | undefined&amp;gt;(undefined);

  useEffect(() =&amp;gt; {
    setData(result);
  }, [result]);

  return data;
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now state can be retrieved like this:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="typescript" data-long&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;TypeScript&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-typescript"&gt;import {uiStore} from &amp;#039;@/store/ui&amp;#039;;
import {useStore} from &amp;#039;@/hooks/useStore&amp;#039;;

const visiblePanels = useStore(uiStore, (state) =&amp;gt; state.dashboard.visiblePanels); ✅ correct
const visiblePanels = uiStore((state) =&amp;gt; state.dashboard.visiblePanels); // ❌ wrong!&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A problem is, nothing in this setup stops someone from using the wrong pattern. Moreover, the wrong way is the default in normal conditions, so any new developer or AI model are likely to use it. Here is where ESLint magic comes useful:&lt;/p&gt;
&lt;div class="e2-code-block" data-language="javascript" data-long&gt;&lt;div class="e2-code-header"&gt;&lt;span class="e2-code-language"&gt;JavaScript&lt;/span&gt;&lt;button class="e2-code-copy" type="button" aria-label="Copy code to clipboard" data-copy-text="Copy" data-copied-text="Copied!" data-failed-text="Failed"&gt;&lt;span class="e2-svgi"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"&gt;&lt;mask id="cutout"&gt;&lt;rect width="100%" height="100%" fill="white"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" stroke-width="1.33" rx="1" fill="black" stroke="black"/&gt;&lt;/mask&gt;&lt;rect x="5.25" y="1.75" width="9" height="9" rx="1" stroke-width="1.33" fill="none" mask="url(#cutout)"/&gt;&lt;rect x="1.75" y="5.25" width="9" height="9" rx="1" stroke-width="1.33" fill="none"/&gt;&lt;/svg&gt;
&lt;/span&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code class="hljs language-javascript"&gt;// eslint.config.mjs

export default defineConfig([
  // ...necessary plugins here...
  {
    rules: {
      &amp;#039;no-restricted-syntax&amp;#039;: [
        &amp;#039;error&amp;#039;,
        {
          selector: &amp;quot;CallExpression[callee.type=&amp;#039;Identifier&amp;#039;][callee.name=/^(?!use).*Store$/]&amp;quot;,
          message: &amp;#039;Do not call store functions directly — use useStore(store, selector) from @/hooks instead.&amp;#039;,
        },
      ],
    },
  },
]);&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This way we tell ESLint to watch for any invocation of functions whose name ends with a &lt;span class="inline-code"&gt;Store&lt;/span&gt;, except for &lt;span class="inline-code"&gt;useStore&lt;/span&gt; — our custom hook. Direct usage will be flagged, so the new devs or models will be able to correct themselves. Surely, someone could write a store with a name different from &lt;span class="inline-code"&gt;useSomethingStore&lt;/span&gt;, but this naming format is common and default by docs, so we stay on a safe ground here.&lt;/p&gt;
&lt;p&gt;With this approach, you can enforce any code style, variable usage rule, import restriction, or architectural constraint. Add them, use them, and may your code be impossible to write in a wrong way.&lt;/p&gt;
</description>
</item>


</channel>
</rss>