Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Exploring Type-Informed Lint Rules in Rust base...

Exploring Type-Informed Lint Rules in Rust based TypeScript Linters

Exploring Type-Informed Lint Rules in Rust based TypeScript Linters | Rust製TypeScript Linterにおける型情報Lintルールの模索

This slides are used at TSKaigi 2024 | TSKaigi 2024 にて利用した資料です。(20m)

Update requests are welcome anytime.

## References

- Rust-Based JavaScript Linters: Fast, But No Typed Linting Right Now
https://www.joshuakgoldberg.com/blog/rust-based-javascript-linters-fast-but-no-typed-linting-right-now/
- Rewriting TypeScript in Rust? You'd have to be... | Total TypeScript https://www.totaltypescript.com/rewriting-typescript-in-rust
- Let's Make a Generic Inference Algorithm by Ryan Cavanaugh - GitNation https://portal.gitnation.org/contents/lets-make-a-generic-inference-algorithm
- Tour de Source: TypeScript ESLint - Sourcegraph https://sourcegraph.com/notebooks/Tm90ZWJvb2s6MTA2OA==
- Rust製TypeScriptコンパイラstcの現状と今後 | メルカリエンジニアリングhttps://engineering.mercari.com/blog/entry/20230606-b059cd98c3/

unvalley

May 11, 2024
Tweet

More Decks by unvalley

Other Decks in Programming

Transcript

  1. Exploring Type-Informed Lint Rules in Rust based TypeScript Linters 1

    Rust製TypeScript Linterにおける 型情報Lintルールの模索 TSKaigi 2024 @unvalley_
  2. - Rust / TypeScript - A core member of Biome

    - Interested in Developer Tools 2 @unvalley_
  3. @typescript-eslint/switch-exhaustiveness-check 11 union型・enumが、switch文で全ケースを網羅しているか検査するルール type Day = 'Monday' | 'Tuesday' |

    'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday'; declare const day: Day; let result = 0; switch (day) { case 'Monday': result = 1; break; }
  4. @typescript-eslint/switch-exhaustiveness-check 12 union型・enumが、switch文で全ケースを網羅しているか検査するルール type Day = 'Monday' | 'Tuesday' |

    'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday'; declare const day: Day; let result = 0; switch (day) { // ❌ ^^^ case 'Monday': result = 1; break; } Switch is not exhaustive. Cases not matched: "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday" | "Sunday" > Add branches for missing cases.
  5. @typescript-eslint/strict-boolean-expressions 15 boolean 式で特定の型を許可しないルール // nullable strings are considered unsafe

    let str: string | null = null; if (!str) { // ❌ ^^^^^ console.log('str is empty'); } Unexpected nullish value in conditional. The condition is always false.
  6. @typescript-eslint/no-floating-promises 18 Promise を返す関数・メソッドが、適切にハンドルされているか検査するルール function returnPromise() { return new Promise((resolve,

    reject) => { setTimeout(() => resolve("data"), 1000); }); } returnPromise(); // ? returnPromise() // ? .then(data => console.log(data)) .catch(err => console.error(err));
  7. @typescript-eslint/no-floating-promises 19 Promise を返す関数・メソッドが、適切にハンドルされているか検査するルール function returnPromise() { return new Promise((resolve,

    reject) => { setTimeout(() => resolve("data"), 1000); }); } returnPromise(); // ? returnPromise() // ? .then(data => console.log(data)) .catch(err => console.error(err)); エラー処理されていない状態で作成される Promise のこと。 「操作順序が不適切になること」や「Promise rejectionsの無視」など の問題を引き起こす可能性がある
  8. @typescript-eslint/no-floating-promises 20 Promise を返す関数・メソッドが、適切にハンドルされているか検査するルール function returnPromise() { return new Promise((resolve,

    reject) => { setTimeout(() => resolve("data"), 1000); }); } // ❌ returnPromise(); ^^^^^^^^^^^^^^^ // ✅ returnPromise() .then(data => console.log(data)) .catch(err => console.error(err)); Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the void operator.
  9. 21 Tips in typescript-eslint : strictTypeChecked strictTypeChecked > recommendedTypeChecked >

    recommended // eslint.config.js export default tseslint.config( ...tseslint.configs.strictTypeChecked ); https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/configs/strict-type-checked.ts
  10.  Biome - One Toolchain for Your Web Project - パフォーマンスに優れたRust製のWebツールチェーン

    - Linterは、200以上のルールを持つ(JS/TS/JSX/CSS) - Formatterは、Prettier互換(97% in JS/TS/JSX) - スポンサー(募集中!) - 時雨堂, トヨクモ, 要, nanabit, vital, GitHub sponsors - Open Collective : https://opencollective.com/biome 25
  11. Rust製TypeScript Linterの利用拡大 26 Biome stars : 10.8k npm downloads :

    296,728 (weekly) Oxc (oxlint) stars : 9.0k npm downloads : 49,930 (weekly)
  12. Rust製TypeScript Linterの利用拡大 27 Biome stars : 10.8k npm downloads :

    296,728 (weekly) Oxc (oxlint) stars : 9.0k npm downloads : 49,930 (weekly) Deno (deno_lint) stars : 93k (1.5k) npm downloads : N/A
  13. Rust製TypeScript Linterの利用拡大 28 Biome stars : 10.8k npm downloads :

    296,728 (weekly) Oxc (oxlint) stars : 9.0k npm downloads : 49,930 (weekly) Deno (deno_lint) stars : 93k (1.5k) npm downloads : N/A
  14. 31 1. Source Code 2. Lex 3. Parse 4. AST

    5. Lint(型情報が不要なLintを実行可能)
  15. 32 1. Source Code 2. Lex 3. Parse 4. AST

    5. Lint(型情報が不要なLintを実行可能)
  16. 33 1. Source Code 2. Lex 3. Parse 4. AST

    5. Lint(型情報が不要なLintを実行可能) 6. Query Type Information(型情報の取得) 7. AST and Type Information(ASTと型情報) 8. Type Informed Lint(型情報Lintを実行可能)
  17. 1. Source Code 2. Lex 3. Parse 4. AST 5.

    Lint(型情報が不要なLintを実行可能) 6. Query Type Information(型情報の取得) 7. AST and Type Information(ASTと型情報) 8. Type Informed Lint(型情報Lintを実行可能) 34
  18. 35 Query Type Information AST and Type Information Type Informed

    Lint - NodeへのVisit typescript-eslintが作るASTを使って、関数呼び出しの NodeにVisitする - Promiseの検出 作成したASTを使って、構文的にPromiseを返してそうな関 数・メソッド呼び出しに該当する Nodeを特定し、そのNodeに 該当するTS Compiler APIのASTのNodeを特定して、型情 報を取得する - Promiseのハンドリング確認 検出されたPromiseが適切にハンドルされているか (then, catch, finally のいずれかで処理されているか、または await されてい るか)を型情報を利用して確認する - 型情報Lintルール実行(診断) Promiseが適切にハンドルされていない場合、 Error または Warnを表示する ざっくり@typescript-eslint/no-floating-promises
  19. Rust製TypeScript Linterが、型情報を取得する方法 49 1. TypeScript Compilerの利用 2. Alternative TypeScript Compilerの利用

    3. Type Inference(サブセット)の実装 複雑な型システム・(v1.8以降)仕様書が無い・Microsoftの資本力への追従
  20. stc (dudykr/stc) 50 - 高速なTypeScript Type Checker - swcでLex /

    Parseを行い、stcでType Checkを行う - tscの挙動を仕様として実装が行われており、独自の構文拡張は未実装 - 2024年5月現在、開発は中止されている😢 - “Closing as the stc is now abandoned. TypeScript was not something that I could follow up on in an alternative language.” github.com/swc-project/swc/issues/571 swcのkdy1氏が開発を進めていたRust製のAlternative TypeScript Compiler
  21. ezno (kaleidawave/ezno) 52 kaleidawave氏が開発しているRust製のAlternative TypeScript Compiler - Rust製のJavaScript CompilerとTypeScript Checkerで、静的解析と実行時の

    パフォーマンスに重点を置く - TypeScriptの型注釈を理解し、JavaScriptに対しても型チェックを行う - ソースコードから最大限の知識を得るアイデアに積極的で、無効なプロパティの特 定・デッドコードの検出も行う(Linter的な振る舞い) - stcとは異なり、TypeScript との1-to-1の代替は目指していない
  22. Type Inference(サブセット)の実装 54 - TypeScriptのType Inference(型推論)のサブセット実装を行う - Linterが生成するASTで型情報を含められるなら、外部ツールとの連携を考え ずに済むのでパフォーマンス(速度)や複雑さの観点で嬉しい -

    近いツールだと Ruff(Python Tools) や eznoなどが行なっている - ただ当然、Generics, Conditional Types, Recursive Types, Mapped Types, Utility Typesなどよく使われる高度な型推論の対応も必要になる
  23. 58 // All cases are errors with --isolatedDeclarations // ❌

    Expressions must not use information outside the expression export const nBad = Math.random(); export const lOk = 1; // ❌ Expressions must not reference other variables export const refConst = lOk; // ❌ Inferred return types are not supported export function noReturn(a: number) { } // ❌ Spread operators and mutable arrays are not supported export const newPoint = { ...point }; export const values = [1, 2, 3]; https://github.com/microsoft/TypeScript/pull/58201
  24. 59 // ✅ All cases are OK with --isolatedDeclarations export

    const nOk: number = Math.random(); export const lOk = 1; export function explicitReturnOk(a: number): void { } export const colors = { 'blue': 0x0000FF, 'red': 0xFF0000, 'green': 0x00FF00, } as const export const directions = ["UP", "DOWN", "LEFT", "RIGHT"] as const export const component = (props: { name: string }): string => ""; https://github.com/microsoft/TypeScript/pull/58201
  25. 62 // Normal TypeScript // a.ts ----------------------------------------------------- export function returnPromise()

    { // 返り値の型を推論する必要がある return new Promise((resolve, reject) => { setTimeout(() => resolve("data"), 1000); }); } // b.ts ----------------------------------------------------- import { returnPromise } from “a”; returnPromise(); // ? returnPromise() // ? .then(data => console.log(data)) .catch(err => console.error(err));
  26. 63 // with --isolatedDeclarations // a.ts --isolatedDeclarationsが、ReturnTypeを明示------------ export function returnPromise():

    Promise<string> { // 型注釈 return new Promise((resolve, reject) => { setTimeout(() => resolve("data"), 1000); }); } // b.ts ---------------------------------------------------- import { returnPromise } from “a”; // returnPromiseの型推論は不要 returnPromise(); // ❌ returnPromise() // ✅ .then(data => console.log(data)) .catch(err => console.error(err));
  27. 64 // with --isolatedDeclarations // a.ts --isolatedDeclarationsが、ReturnTypeを明示------------ export function returnPromise():

    Promise<string> { // 型注釈 return new Promise((resolve, reject) => { setTimeout(() => resolve("data"), 1000); }); } // b.ts ---------------------------------------------------- import { returnPromise } from “a”; // returnPromiseの型推論は不要 returnPromise(); // ❌ returnPromise() // ✅ .then(data => console.log(data)) .catch(err => console.error(err)); JsFunctionDeclaration { ... return_type_annotation: TsReturnTypeAnnotation { ty: TsReferenceType { name: JsReferenceIdentifier { value_token: [email protected] "Promise", }, type_arguments: TsTypeArguments { ts_type_argument_list: TsTypeArgumentList [ TsStringType { string_token: [email protected] "string", }, ], ...
  28. 余談:Slow Types in JSR 68 TypeScript内で、以下に当てはまる型 - 明確に定義されていない型 - 推論に広範な処理が必要なほど複雑な型

    型検査の高速化 / ドキュメント・型定義ファイル生成の安定化 https://github.com/jsr-io/jsr/issues/444#issuecomment-2079772908 型注釈によって明示して除去 --isolatedDeclarations と似た概念
  29. Rust製TypeScript Linter(型情報Lintルール)の今後の可能性 71 Type Inference のサブセット実装 --isolatedDeclarationsが活用できると良い Type Inference ?(tsserverを利用した型情報の取得

    ) eznoとの再連携も今後の可能性としては考えられる 2024年5月現在、保留されている印象 no-floating-promisesは、#13376 “async / await: nowait keyword?” の解決に委ねる?
  30. まとめ:Rust製Linterにおける型情報Lintルールの模索 77 - 型情報Lintルールは、コードの安全性を高める上で重要だが、 TypeScriptの型検査が必要なためLint実行時間を遅くさせてしまう - Rust製TypeScript Linterの型情報Lintルールの実装方法を紹介 1. TypeScript

    Compilerの利用 2. Alternative TypeScript Compilerの利用 3. Type Inference(サブセット)の実装 (with --isolatedDeclarations) - 今後のRust製TypeScript LinterとTypeScriptの動向を要チェック
  31. Acknowledgement 78 - ty and nissy-dev from Biome - sosukesuzuki

    from Prettier - All Biome (Rome), oxc, deno_lint, ESLint and typescript-eslint contributors - Especially, ematipico, conaclos, Boshen
  32. References 79 - Rust-Based JavaScript Linters: Fast, But No Typed

    Linting Right Now https://www.joshuakgoldberg.com/blog/rust-based-javascript-linters-fast-but-no-typed-linti ng-right-now/ - Rewriting TypeScript in Rust? You'd have to be... | Total TypeScript https://www.totaltypescript.com/rewriting-typescript-in-rust - Let's Make a Generic Inference Algorithm by Ryan Cavanaugh - GitNation https://portal.gitnation.org/contents/lets-make-a-generic-inference-algorithm - Tour de Source: TypeScript ESLint - Sourcegraph https://sourcegraph.com/notebooks/Tm90ZWJvb2s6MTA2OA== - Rust製TypeScriptコンパイラstcの現状と今後 | メルカリエンジニアリング https://engineering.mercari.com/blog/entry/20230606-b059cd98c3/