跳到正文
DEV Community· Stefan Kuburovic·· 2 小時前AI 評分48

用 ng-checkup 檢查 19 個開源 Angular 程式碼庫:編譯器看不到的錯誤

I ran a checker on 19 open-source Angular codebases. Here are the bugs no compiler sees.

AI 導讀

作者以 ng-checkup 檢查 19 個固定在指定提交版本的公開 Angular 和 AngularJS 程式碼庫,找出編譯器和一般測試難以發現的問題。檢查涵蓋不存在的翻譯鍵、無效的表單標籤連結、空測試及未執行的非同步斷言。ng-checkup 以文字和語法樹分析程式碼,不會建置或執行程式。

正文

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.

1. The translation key that doesn't exist

message: this.translateService.instant('walletconnect.error'),

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

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.

The same app had four more: a key no language has, and three that only exist in English.

2. The label that points nowhere

<label class="text-sm text-gray-600" for="currentPassword">Current password</label>
<input class="input-text" [(ngModel)]="currentPassword" name="currentPassword" type="password" required>

It looks fine and it renders fine. But for points at an id 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.

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

3. The test that checks nothing

it('should replay the last emitted value', () => {
  testScheduler.run(({ cold, expectObservable }) => {});
});

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.

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

Why these three

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 ng-checkup 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.

I've sent fixes for all three: vendure storefront #97, airgap-wallet #350 and rx-angular #1972.

Check your own app

npx ng-checkup-cli

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

npx ng-checkup-cli --report checkup.html

To keep it from getting worse without fixing everything first, record today's numbers and fail CI only when one goes up:

npx ng-checkup-cli baseline
npx ng-checkup-cli --fail-on regression

On a pull request, --since origin/main keeps only the findings in the files your branch changed.

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.

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.

來源:DEV Community · dev.to