<?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: Stefan Kuburovic</title>
    <description>The latest articles on DEV Community by Stefan Kuburovic (@stefankuburovic).</description>
    <link>https://dev.to/stefankuburovic</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F572775%2Fa347a7f0-15dd-419d-8238-d748f464e49c.jpeg</url>
      <title>DEV Community: Stefan Kuburovic</title>
      <link>https://dev.to/stefankuburovic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/stefankuburovic"/>
    <language>en</language>
    <item>
      <title>I ran a checker on 19 open-source Angular codebases. Here are the bugs no compiler sees.</title>
      <dc:creator>Stefan Kuburovic</dc:creator>
      <pubDate>Tue, 03 Nov 2026 22:26:48 +0000</pubDate>
      <link>https://dev.to/stefankuburovic/i-ran-a-checker-on-19-open-source-angular-codebases-here-are-the-bugs-no-compiler-sees-1d0f</link>
      <guid>https://dev.to/stefankuburovic/i-ran-a-checker-on-19-open-source-angular-codebases-here-are-the-bugs-no-compiler-sees-1d0f</guid>
      <description>&lt;p&gt;Some bugs survive everything. The build is green, the tests pass, the reviewer approves, and the user still sees something broken. I spent the last weeks teaching a tool to find a few of them, and tested every rule against 19 public Angular and AngularJS codebases pinned at fixed commits. Here is what came out, and how to check your own app in a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The translation key that doesn't exist
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;instant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;walletconnect.error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The English bundle has &lt;code&gt;walletconnect.connection_error&lt;/code&gt;, not &lt;code&gt;walletconnect.error&lt;/code&gt;. When a lookup fails, ngx-translate (and Transloco) render the key itself, so the user reads &lt;code&gt;walletconnect.error&lt;/code&gt; on the error page. The compiler can't see it, the key is just a string. A test that renders the component passes too.&lt;/p&gt;

&lt;p&gt;Checking this by hand stops working the moment an app loads more than one bundle. Feature bundles usually declare the same top-level namespace with different children, so the bundles have to be merged deeply per language before any lookup means something. A shallow merge makes keys look missing that are not.&lt;/p&gt;

&lt;p&gt;The same app had four more: a key no language has, and three that only exist in English.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The label that points nowhere
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-sm text-gray-600"&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"currentPassword"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Current password&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"input-text"&lt;/span&gt; &lt;span class="na"&gt;[(ngModel)]=&lt;/span&gt;&lt;span class="s"&gt;"currentPassword"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"currentPassword"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"password"&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks fine and it renders fine. But &lt;code&gt;for&lt;/code&gt; points at an &lt;code&gt;id&lt;/code&gt; the input doesn't have, so the label is attached to nothing. Clicking it doesn't focus the field, and a screen reader announces the field without its name. That fails WCAG 1.3.1 and 4.1.2, level A, which since June 2025 the European Accessibility Act asks a lot of businesses to meet.&lt;/p&gt;

&lt;p&gt;Across the 19 codebases, every repository but one had at least one defect of this kind. The most common were &lt;code&gt;(click)&lt;/code&gt; on a &lt;code&gt;div&lt;/code&gt; with no keyboard handler, and images without &lt;code&gt;alt&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The test that checks nothing
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should replay the last emitted value&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;testScheduler&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="nx"&gt;cold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expectObservable&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The name promises a check, the body has none. It passes on every run, shows up green in every report, and counts towards coverage. Empty tests usually start as a placeholder or lose their body in a refactor, and nobody notices because nothing ever fails.&lt;/p&gt;

&lt;p&gt;The same check also finds the subtler kind: an &lt;code&gt;expect()&lt;/code&gt; inside a &lt;code&gt;subscribe()&lt;/code&gt; on an asynchronous source, in a test that takes no &lt;code&gt;done&lt;/code&gt;, returns no promise and flushes no timers. The test returns before the value arrives, so the assertion never runs inside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why these three
&lt;/h2&gt;

&lt;p&gt;They share one property: nothing in the normal toolchain looks at them. TypeScript sees strings and objects. The test runner sees a test that returned. The reviewer sees code that looks right. That's the gap I built &lt;a href="https://github.com/stefankuburovic/ng-checkup" rel="noopener noreferrer"&gt;ng-checkup&lt;/a&gt; for. It reas an Angular codebase as text and syntax trees, never builds or runs it, and reports what blocks the next upgrade and what nobody else notices.&lt;/p&gt;

&lt;p&gt;I've sent fixes for all three: &lt;a href="https://github.com/vendurehq/storefront-angular-starter/pull/97" rel="noopener noreferrer"&gt;vendure storefront #97&lt;/a&gt;, &lt;a href="https://github.com/airgap-it/airgap-wallet/pull/350" rel="noopener noreferrer"&gt;airgap-wallet #350&lt;/a&gt; and &lt;a href="https://github.com/rx-angular/rx-angular/pull/1972" rel="noopener noreferrer"&gt;rx-angular #1972&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check your own app
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ng-checkup-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No install, no config. It needs Node 22, works on a fresh clone without &lt;code&gt;node_modules&lt;/code&gt;, and reads your code without running any of it. For the full report as one page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ng-checkup-cli &lt;span class="nt"&gt;--report&lt;/span&gt; checkup.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep it from getting worse without fixing everything first, record today's numbers and fail CI only when one goes up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx ng-checkup-cli baseline
npx ng-checkup-cli &lt;span class="nt"&gt;--fail-on&lt;/span&gt; regression
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a pull request, &lt;code&gt;--since origin/main&lt;/code&gt; keeps only the findings in the files your branch changed.&lt;/p&gt;

&lt;p&gt;The translation and accessibility checks never change the score. They're findings and baseline measures, so adopting them on a large app doesn't turn the dashboard red on day one, but CI fails the day a new one lands.&lt;/p&gt;

&lt;p&gt;It also ships as a GitHub Action, a GitLab CI/CD component, a VS Code extension and an MCP server for Claude Code, Cursor and Copilot. It's MIT licensed. If a rule gets something wrong on your code, I'd like to hear about it: every rule is tested against those 19 codebases, and a false positive is a bug.&lt;/p&gt;

</description>
      <category>angular</category>
      <category>a11y</category>
      <category>testing</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
