ყოველ TypeScript გუნდს აქვს წესი @ts-ignore-ზე. ჩვენსაც აქვს, და ის მუშაობს: ნულოვანი შემთხვევა 610 საწყის ფაილში.
შემდეგ ვეძებე რაღაცას, რასაც სტილის სახელმძღვანელოში ვერავინ წერს:
grep -rn --include='*.ts' --include='*.tsx' \
--exclude-dir=node_modules --exclude-dir=.next \
'as unknown as' apps packages | wc -l
117.
ცხადი გამორთვა ნულია, ნაგულისხმევი კი — ას თვრამეტი. ეს თანაფარდობა შემთხვევითი არ არის — ასეთია შედეგი, როდესაც აკრძალვა მხოლოდ იმ სინტაქსს ფარავს, რომელიც ადამიანებს გრეპისთვის ახსოვთ.
რიცხვები რეალური რეპოზიტორიიდან
| შაბლონი | გამოჩენები წყაროში |
|---|---|
@ts-ignore |
0 |
@ts-expect-error |
0 |
as any |
3 |
as unknown as |
117 |
eslint-disable* |
53 |
console.log |
4 |
კიდევ ორი ფაქტი, რომელიც ღირს ცოდნას. 53 eslint-disable არ არის 53 გადაწყვეტილება: მათგან 42 ერთი წესია (@next/next/no-img-element), რაც <img> თეგებთან დაკავშირებული ერთი არქიტექტურული არჩევანია, გამეორებული. ხოლო კასტები თანაბრად არ არის გადანაწილებული — ისინი ჯგუფდებიან:
| ფაილი | as unknown as |
|---|---|
api/src/routes/admin.ts |
14 |
api/src/stories/worker.ts |
11 |
api/src/app.ts |
7 |
api/src/assistant/worker.ts |
6 |
web/src/lib/messages/index.ts |
5 |
api/src/stories/production.ts |
5 |
unknown. i18n რუკა ინდექსებს სტრიქონებს გამოთვლილი გასაღებით. ყველა ეს საზღვარია — ადგილები, სადაც გაშვების დროის ფორმა ხვდება კომპილაციის დროის ტიპს და ვერავინ დაწერა პარსერი.
რატომ გადარჩა ეს კოდის რევიუს
as unknown as Foo ორი კასტია. პირველი ტიპს unknown-ამდე ასუფთავებს, რაც გულწრფელია; მეორე მას უკან აძლევს იმას, რაც გინდა, და ეს უკვე ტყუილია. ერთად ისინი სემანტიკურად იდენტურია as any-ის, სამი უპირატესობით:
-
ის კომპილირდება
no-explicit-any-ით. თუ თქვენი ლინტის კონფიგურაცია კრძალავსany-ს — ბევრი კრძალავს — ეს არის გვერდის ავლის გზა. - ის მიჩნეულია გააზრებულად. რევიუერი, რომელიც diff-ს ათვალიერებს, კასტს ხედავს, არა გამორთვის კომენტარს. რევიუში არ არის წითელი ალამი, რომლის გრეპიც შეიძლება.
-
ამას სწვდება აგენტი. როდესაც მოდელს სთხოვენ ტიპის შეცდომის გაქრობას,
as unknown asარის მინიმალური რედაქტირება, რომელიც აკმაყოფილებს კომპილატორს და არ ამატებს კომენტარს, რომელზეც ადამიანმა შეიძლება იკითხოს.
სწორედ ამიტომ იზრდება რაოდენობა AI-ს დახმარებით შექმნილ ბრენჩებზე. ინსტრუმენტი ავთენტურად ბოროტი არ არის; ის ზუსტად იმას აკეთებს, რაც სთხოვეს. აკრძალვის სია უბრალოდ არ ფარავს გასასვლელს, რომელიც მან იპოვა.
კარიბჭე, რომელიც დავამატე
გრეპი CI-ში, ცხადი დაშვებულების სიით — რადგან მიზანი არ არის ნული ამ კვირაში, არამედ "აღარასდროს 117".
#!/usr/bin/env bash
# fail when the double-cast count grows past the recorded budget
budget=$(cat .cast-budget)
actual=$(grep -rn --include='*.ts' --include='*.tsx' \
--exclude-dir=node_modules --exclude-dir=.next \
'as unknown as' apps packages | grep -v 'as-unknown-as-allow' | wc -l)
echo "as unknown as: ${actual} (budget ${budget})"
if [ "$actual" -gt "$budget" ]; then
echo "New double casts. Narrow the type, or add the line to the allowlist with a reason."
exit 1
fi
სამი თვისება უფრო მნიშვნელოვანია, ვიდრე თავად წესი:
- ბიუჯეტი, არა აკრძალვა. მკაცრი ნული პირველ დღეს ნიშნავს 117-სტრიქონიან დაშვებულების სიას, რომელსაც ვერავინ წაიკითხავს. რატჩეტი — ბიუჯეტი მცირდება, როდესაც ერთს ასწორებთ, და არასდროს იზრდება — ინარჩუნებს რიცხვს სანდოს.
-
დაშვებულების სია მიზეზს ატარებს.
// as-unknown-as-allow: stripe webhook payload is validated upstreamარის რევიუს ღირსი წინადადება. ცარიელი ჩანაწერი არის კომენტარი, რომელიც არაფერს ამბობს. - ის მუშაობს იმავე job-ში, რაც typecheck, ამიტომ რაოდენობა ჩანს იმ PR-ში, რომელიც მას ცვლის.
თუ თქვენს კონფიგურაციაში დაყენებულია typescript-eslint, ეკვივალენტური წესი არის restricted-syntax სელექტორი:
'no-restricted-syntax': ['error', {
selector: 'TSAsExpression > TSUnknownKeyword',
message: 'Double cast. Narrow with a parser or a guard instead.',
}]
მაინც შევინარჩუნებდი გრეპს: ის მუშაობს რეპოზიტორიებში ESLint-ის გარეშე, CI სკრიპტებში და იმ დღეს, როდესაც პარსერის პლაგინი განახლდება და სელექტორი აღარ დაემთხვევა.
სამი შაბლონი, რომელიც მათი 90%-ის უკან დგას
ხელით წაკითხვისას 117 სამ ჯგუფად იშლება, თითოეულს აქვს ჩანაცვლება, რომელიც ფაილზე დაახლოებით ათ წუთს ითხოვს:
1. გარე მონაცემების დამუშავება. JSON.parse(body) as unknown as JobRow იქცევა სქემის პარსინგად საზღვარზე, და ყველაფერი შემდეგ ნამდვილად ტიპიზებულია:
const JobRow = z.object({ id: z.string(), status: z.enum(['queued', 'running', 'done']) });
const row = JobRow.parse(JSON.parse(body)); // typed, and it fails loudly on drift
2. მონაცემთა ბაზის მწკრივების წაკითხვა დომენის ტიპში. კასტი შეცდომას გადააქვს "სადღაც მოგვიანებით, სტეკის კვალით, რომელიც სვეტს არ ახსენებს" მდგომარეობიდან "აქ, წაკითხვისას" მდგომარეობაში. პატარა mapper-ფუნქცია თითო ცხრილზე ჯობია კასტზე თითო გამომძახებელთან.
3. თარგმანების რუკის ინდექსირება გამოთვლილი გასაღებით. messages[key] as unknown as string ჩვეულებრივ საჭიროებს satisfies Record<Locale, string>-ს განსაზღვრებაზე და ტიპიზებულ accessor-ს — კასტი ქრება, რადგან ფორმა ყოველთვის არსებობდა, უბრალოდ დაუმტკიცებელი.
როდის არის ორმაგი კასტი მისაღები
-
სატესტო ორმაგები. დიდი ინტერფეისის ნაწილობრივი იმპლემენტაცია კასტის ფორმის პრობლემაა; შეინახეთ ის ტესტებში და დაუშვით ბიუჯეტმა
*.test.ts-ის უგულებელყოფა. -
მოდულის ავგმენტაცია ტიპიზირებული არმქონე მესამე მხარის პაკეტებისთვის. თქვენ აცხადებთ იმას, რაც პაკეტმა უნდა გამოაცხადოს. ჩადეთ ერთ
types/ფაილში კომენტარით, და ის არასდროს გამოჩნდება ორჯერ. -
მიგრაციის საზღვრები. როდესაც სვეტის ტიპი იცვლება, ერთი კასტი მარკერით
// migration: remove after 2026-12უფრო იაფია, ვიდრე პარალელური ტიპების ხე.
ყველაფერი დანარჩენი არის პარსერი, რომელიც ჯერ არ დაგიწერიათ.
ხშირად დასმული კითხვები
ნელია თუ არა as unknown as გაშვების დროს? არა. TypeScript ორივე კასტს შლის; ხარჯი მთლიანად იმაშია, რასაც კომპილატორი აღარ ამოწმებს.
ანაცვლებს თუ არა მას satisfies? ზოგჯერ. satisfies ამტკიცებს, რომ მნიშვნელობა ემთხვევა ტიპს მის გაფართოების გარეშე — რაც ზუსტად i18n შემთხვევაა — მაგრამ ვერ გადაარჩენს მნიშვნელობას, რომელიც თქვენ არ შეგიქმნიათ.
რატომ არ კრძალავთ მთლიანად? იმიტომ, რომ აკრძალვა მიგრაციის გზის გარეშე წარმოშობს დაშვებულების სიას, რომელიც პრობლემაზე გრძელია. ბიუჯეტი არის მიგრაციის გზა.
დაიჭერდა თუ არა ამ შემთხვევებს მკაცრი tsconfig? noUncheckedIndexedAccess და exactOptionalPropertyTypes აშორებს მიზეზთა ნაწილს, რის გამოც ადამიანები კასტს სწვდებიან. ისინი ასევე პირველ დღეს ბევრ წითელს წარმოშობს. რატჩეტეთ იგივე გზით.
ზემოთ მოყვანილი რიცხვები ერთი რეპოზიტორიიდან და ერთი grep-იდან მოვიდა; თქვენი თანაფარდობა განსხვავდება, და საინტერესო რიცხვი არის თანაფარდობა, არა ჯამი. თუ ჩაატარებთ იგივე აუდიტს და ორმაგი კასტი ნულია, მინდა ვიცოდე, როგორ გამოიყურება თქვენი საზღვრები, რადგან ეს არის კოდის ბაზა, სადაც პარსერები ყველგანაა.
