Wróć do bloga
TypeScript
10 min czytania

Zbudowałem sprawdzacz typów, który sprawił, że AI przestało mnie okłamywać

AI używa `any`, żeby uciec przed błędem typów. Dodaje eslint-disable. Kłamie ci w oczy. Oto jak naprawiliśmy tę pętlę sprzężenia zwrotnego.

AI nie okłamuje cię, bo chce. Okłamuje cię, bo na to pozwalasz.

Zepsuta pętla sprzężenia zwrotnego

Poproś Claude Code o dodanie funkcji. Robi to. Uruchom ESLint - przechodzi. Uruchom TypeScript - błędy. Poproś Claude Code o naprawienie błędów TypeScript. Robi to. Uruchom ESLint - teraz to się nie udaje. Tam i z powrotem. Trzy iteracje. AI jest pewne siebie na każdym kroku. Za każdym razem: "To jest naprawione."

Nigdy nie było naprawione.

To była gra w kreta z narzędziem, które uruchamia jeden linter na raz i nigdy nie widzi pełnego obrazu.

AI naprawia błąd TypeScript, pisząc jedną linię i mówi ci: "Błąd typów jest rozwiązany."

typescript
1const result = data as unknown as MyExpectedType;

Tak. Technicznie. Tak jak zaklejenie czujnika dymu taśmą rozwiązuje alarm pożarowy.

To właśnie po to zbudowałem @next-vibe/checker.

Problem z `any`

Dlaczego 98% bezpieczeństwa typów to to samo co 0%

System typów TypeScript to graf. Każdy typ przepływa od definicji do użycia. Jeśli masz funkcję, która zwraca `string`, wywołujący wie, że to string. Cały łańcuch jest sprawdzany.

any
result
transformed
output
Component
hook
schema

Jeden typ `any` rozprzestrzenia się przez graf, korumpując wnioskowanie typów poniżej

`any` to dziura w grafie.

Zmienna typowana jako `any` mówi kompilatorowi: zatrzymaj sprawdzanie tutaj. Nie tylko dla tej zmiennej - dla wszystkiego, co dotyka tej zmiennej. Błąd nie pojawia się przy `any`. Pojawia się trzy pliki dalej, gdy niezwiązany refactoring psuje założenie, które nigdy nie było egzekwowane.

0

błędy TypeScript

47

użycia `any`

Zero błędów TypeScript nie znaczy nic, jeśli masz 47 niezweryfikowanych użyć `any`.

`as unknown as Whatever` jest gorsze. To podwójne twierdzenie typowe. Mówisz kompilatorowi: wiem, że to jest błędne, i mimo to się przez to przekonuję. To ulubiona furtka AI.

Zakazane wzorce w tej bazie kodu:

anyno-explicit-any
as unknown asno-unsafe-assignment
@ts-expect-errorban-ts-comment
throwrestricted-syntax
unknownrestricted-syntax
objectrestricted-syntax

Nie ostrzeżenia. Błędy. Sprawdzenie się nie udaje. Claude Code musi naprawić podstawową przyczynę lub nie może dostarczyć.

Powód, dla którego są to błędy, a nie ostrzeżenia, jest psychologiczny tak samo jak techniczny. Modele AI traktują ostrzeżenia jako opcjonalne. Błędy zamykają pętlę.

Przedstawiamy @next-vibe/checker

Jedno polecenie. Trzy narzędzia. Brak furtek.

bash
1$ vibe check

To jest polecenie. Jedno polecenie. Uruchamia trzy narzędzia równolegle i daje ci jedną ujednoliconą listę błędów.

Oxlint

Linter oparty na Rust. Setki reguł. Działa w milisekundach nawet na dużych bazach kodu.

ESLint

Rzeczy, których Oxlint jeszcze nie robi: lint haków React, reguły kompilatora React, sortowanie importów.

TypeScript

Pełne sprawdzanie typów. Nie tylko plik, który edytujesz - cały graf.

równolegle

Ujednolicona lista błędów

Jedno wyjście. Naprawiaj, aż będzie czysto.

Na tej bazie kodu - 4400 plików - pełne TypeScript zajmuje około 12 sekund. Oxlint jest poniżej sekundy. ESLint to kilka sekund. Równoległość sprowadza to do 12.

Udostępnia również polecenie `vibe-check mcp`, które uruchamia serwer MCP z narzędziem `check`. AI nie uruchamia polecenia powłoki - wywołuje narzędzie, które zwraca ustrukturyzowane dane błędów. Paginacja wbudowana. Filtrowanie według ścieżki.

Niestandardowe wtyczki

Linter to dokumentacja. I jest egzekwowany.

Wtyczka jsx-capitalization

Flaguje elementy JSX pisane małymi literami, a komunikat o błędzie mówi ci dokładnie, co zamiast tego zaimportować:

<button><Button> from "next-vibe-ui/ui/button"
<a><Link> from "next-vibe-ui/ui/link"
<p><P> from "next-vibe-ui/ui/typography"

Nie napisałem dokumentacji mówiącej Claude Code, żeby używało `next-vibe-ui`. Nie dodałem tego do systemowego promptu. Za pierwszym razem, gdy Claude Code pisze `<div>` w komponencie, sprawdzacz wyrzuca błąd. Komunikat o błędzie zawiera dokładną ścieżkę importu. Claude Code czyta błąd, stosuje poprawkę i zapamiętuje konwencję.

Wtyczka restricted-syntax

Zakazuje trzech rzeczy:

Instrukcje `throw`

Komunikat o błędzie mówi: "Zamiast tego użyj właściwych wzorców `ResponseType<T>`." Claude Code na to trafia, czyta to, szuka `ResponseType` i przyjmuje właściwy wzorzec obsługi błędów dla reszty zadania.

nagi typ `unknown`

"Zastąp 'unknown' istniejącym typowanym interfejsem. Dostosuj się do typów w bazie kodu, zamiast konwertować lub odtwarzać." To powstrzymuje Claude Code przed pisaniem generycznych furtek typowych.

nagi typ `object`

`object` jest prawie zawsze błędny. Albo znasz kształt - napisz interfejs - albo masz `Record<string, SomeType>`. Surowy `object` to sygnał, że AI się poddało.

Zakazane wzorce

ZAKAZANErestricted-syntax
typescript
1const result = response as unknown as MyType;
POPRAWNE
typescript
1const result: MyType = parseResponse(response);
ZAKAZANEno-explicit-any
typescript
1function foo(x: any): any { return x.data; }
POPRAWNE
typescript
1function foo<T>(response: ResponseType<T>): T | null {
2  if (!response.success) return null;
3  return response.data;
4}
ZAKAZANErestricted-syntax
typescript
1throw new Error("Something failed");
POPRAWNE
typescript
1return fail({
2  message: t("errors.server.title"),
3  errorType: ErrorResponseTypes.INTERNAL_ERROR,
4});

Demo na żywo: wzorzec 3 rund

Obserwuj, jak Claude Code trafia na sprawdzacz trzy razy przed znalezieniem właściwego typu

1

Runda 1 - AI pisze `any`

Zapytaj Claude Code: "Napisz funkcję pomocniczą, która przyjmuje surowy obiekt odpowiedzi API i wyciąga pole danych. Odpowiedź może mieć różne kształty - użyj whatever type makes this work."

Claude Code pisze proste rozwiązanie:

typescript
1export function parseApiResponse(response: any): any {
2  return response.data;
3}
bash
1  1:37  error  Unexpected any.  typescript/no-explicit-any
2  1:44  error  Unexpected any.  typescript/no-explicit-any
3
42 errors found.
2

Runda 2 - AI próbuje `unknown`

Obserwuj, co robi dalej. To jest ważna część. Próbuje następnej drogi ucieczki:

Sprawdzacz zna ten trik.

typescript
1export function parseApiResponse(response: unknown): unknown {
2  return (response as Record<string, unknown>).data;
3}
bash
1  1:37  error  Replace 'unknown' with typed interface.  restricted-syntax
2  1:44  error  Replace 'unknown' with typed interface.  restricted-syntax
3  1:56  error  Replace 'unknown' with typed interface.  restricted-syntax
4
53 errors found.
3

Runda 3 - AI znajduje prawdziwy typ

Teraz Claude Code robi to, co powinno zrobić jako pierwsze. Patrzy, jak istniejące odpowiedzi API są typowane w tej bazie kodu. Znajduje `ResponseType<T>`.

Zero błędów. I funkcja jest teraz faktycznie poprawna.

typescript
1import type { ResponseType } from "@/response-type";
2
3export function parseApiResponse<T>(
4  response: ResponseType<T>
5): T | null {
6  if (!response.success) return null;
7  return response.data;
8}
bash
1  Oxlint: 0 errors
2  ESLint: 0 errors
3  TypeScript: 0 errors
4
50 errors found.

Sprawdzacz tego nie napisał. Ale sprawdzacz zapobiegł skrótowi dwa razy, dopóki Claude Code nie musiało się zmierzyć z faktycznym problemem.

Połączenie z endpointem

Jeden schemat Zod - czterech konsumentów poniżej

Każdy endpoint ma plik definicji. Ten plik zawiera jeden schemat Zod dla żądania i jeden dla odpowiedzi.

Klucz `schema` to walidator Zod. Ten sam schemat Zod staje się:

typescript
1name: requestField(st, {
2  schema: z.string().min(1).max(255),
3  label: "name",
4  description: "description",
5  placeholder: "placeholder",
6}),

Regułą walidacji na endpoincie web API

Typem TypeScript dla parametru wejściowego hooka React

Flagą `--name` w CLI z zastosowanymi ograniczeniami min/max

Opisem parametru w schemacie narzędzia AI

Tu zwykle zabija cię dryf. Aktualizujesz API. Zapominasz zaktualizować schemat narzędzia AI. AI wywołuje endpoint ze starymi nazwami parametrów. Cicho się nie udaje.

Gdy jest jeden schemat, nie ma nic do synchronizowania.

I ponieważ sprawdzacz TypeScript też na tym działa - jeśli zmienisz schemat w sposób, który psuje wnioskowany typ poniżej, dostajesz błąd kompilatora. Schemat narzędzia AI jest sprawdzany typowo. Flagi CLI są sprawdzane typowo. Hook React jest sprawdzany typowo.

245

245 endpointów

0

Zero `any`

0

Zero rzutowań `unknown`

0

Zero `@ts-expect-error`

Nie przez konwencję. Przez sprawdzacz.

Zainstaluj @next-vibe/checker

Działa na każdym projekcie TypeScript. Nie tylko next-vibe.

Sprawdzacz jest dostępny jako samodzielny pakiet npm. Działa na każdym projekcie TypeScript - nie tylko NextVibe. Nie potrzebujesz żadnej innej części frameworka.

bash
1bun add -D @next-vibe/checker\n# or\nnpm install -D @next-vibe/checker

Następnie uruchom:

bash
1vibe-check config    # scaffold check.config.ts\nvibe-check           # run all checks\nvibe-check --fix     # auto-fix linting issues

Integracja MCP

Dodaj go do konfiguracji MCP Claude Code lub Cursor. Teraz Claude Code wywołuje `check` jako narzędzie, a nie polecenie powłoki. Ustrukturyzowane błędy. Paginowane. Filtrowane według ścieżki.

json
1{
2  "mcpServers": {
3    "vibe-check": {
4      "command": "vibe-check",
5      "args": ["mcp"],
6      "env": { "PROJECT_ROOT": "/path/to/project" }
7    }
8  }
9}

Na stronie npm jest też prompt migracyjny. Skopiuj go do Claude Code lub Cursor, a przejrzy twoją bazę kodu, skonfiguruje sprawdzacz i zmigruje cię do zakazanych wzorców.

To jest open source. GPL-3.0 dla frameworka, MIT dla pakietu sprawdzacza.

Zbuduj system tak, żeby kłamstwo było niemożliwe

Przedtem:

Programowanie wspomagane AI było negocjacją. Napraw lint. Och, teraz typy są zepsute. Napraw typy. Teraz jest `any`, którego nie zauważyłeś. Napraw to. Uruchom trzy oddzielne narzędzia. Uzyskaj trzy oddzielne opinie. Nigdy nie wiesz, czy to naprawdę czyste.

Potem:

Jedno polecenie. Jeden tryb awarii. Albo przechodzi, albo nie. AI wie dokładnie, co musi naprawić, ponieważ błędy mówią mu dokładnie, co jest nie tak i co zamiast tego zrobić. Żadnych negocjacji.

Zbuduj system tak, żeby kłamstwo było niemożliwe. Do tego służy sprawdzacz typów.