Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
- Regular expression & Type - Naming Rule Linter
Search
Takepepe
March 04, 2020
Technology
1
490
- Regular expression & Type - Naming Rule Linter
#tsc_api_study #1
正規表現と型推論を突合し、命名規則にルールを導入するツールを紹介
Takepepe
March 04, 2020
Tweet
Share
More Decks by Takepepe
See All by Takepepe
どの様にAIエージェントと 協業すべきだったのか?
takefumiyoshii
2
770
ServerAction で Progressive Enhancement はどこまで頑張れるか? / progressive-enhancement-with-server-action
takefumiyoshii
7
1.2k
App Router への移行は「改善」となり得るのか?/ Can migration to App Router be an improvement
takefumiyoshii
8
3.6k
フロントエンドの書くべきだったテスト、書かなくてよかったテスト
takefumiyoshii
40
17k
Webフロントエンドのための実践「テスト」手法 CodeZine Night #1
takefumiyoshii
24
9.2k
Next.js でリアーキテクトした話 / story-of-re-architect-with-nextjs
takefumiyoshii
12
9k
より速い WEB を目指す Next.js / nextjs-make-the-web-faster
takefumiyoshii
54
20k
フロントエンドの複雑さに耐えるため実践したこと / readyfor-nextjs-first
takefumiyoshii
25
11k
Redux の利点を振り返る
takefumiyoshii
26
8.9k
Other Decks in Technology
See All in Technology
Spec Driven Development入門/spec_driven_development_for_learners
hanhan1978
1
690
ソフトウェアエンジニアとデータエンジニアの違い・キャリアチェンジ
mtpooh
1
330
ソフトウェア品質を支える テストとレビュー再考 / 吉澤 智美さん
findy_eventslides
1
360
MCP サーバーの基礎から実践レベルの知識まで
azukiazusa1
21
9.5k
AIとの協業で実現!レガシーコードをKotlinらしく生まれ変わらせる実践ガイド
zozotech
PRO
2
340
AIでデータ活用を加速させる取り組み / Leveraging AI to accelerate data utilization
okiyuki99
6
1.8k
Playwrightで始めるUI自動テスト入門
devops_vtj
0
170
re:Inventに行くまでにやっておきたいこと
nagisa53
0
1.1k
AIエージェントは「使う」だけじゃなくて「作る」時代! 〜最新フレームワークで楽しく開発入門しよう〜
minorun365
PRO
7
1.1k
[AWS 秋のオブザーバビリティ祭り 2025 〜最新アップデートと生成 AI × オブザーバビリティ〜] Amazon Bedrock AgentCore で実現!お手軽 AI エージェントオブザーバビリティ
0nihajim
2
370
Oracle Database@Google Cloud:サービス概要のご紹介
oracle4engineer
PRO
0
440
ざっくり学ぶ 『エンジニアリングリーダー 技術組織を育てるリーダーシップと セルフマネジメント』 / 50 minute Engineering Leader
iwashi86
9
4.5k
Featured
See All Featured
For a Future-Friendly Web
brad_frost
180
10k
RailsConf 2023
tenderlove
30
1.3k
The World Runs on Bad Software
bkeepers
PRO
72
11k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
VelocityConf: Rendering Performance Case Studies
addyosmani
333
24k
Statistics for Hackers
jakevdp
799
220k
How GitHub (no longer) Works
holman
315
140k
Making the Leap to Tech Lead
cromwellryan
135
9.6k
Git: the NoSQL Database
bkeepers
PRO
431
66k
Done Done
chrislema
186
16k
Leading Effective Engineering Teams in the AI Era
addyosmani
8
870
Transcript
- RegExp & Type - Naming Rule Linter #tsc_api_study @Takepepe
About Me ▪ Takefumi Yoshii / @Takepepe ▪ DeNA /
DeSC Healthcare ▪ Frontend Developer 2
Agenda ▪ 1. 推論内容が見える「ts.TypeChecker」 ▪ 2. TypeScript AST Viewer が教えてくれること
▪ 3. 正規表現で命名を規制する ▪ 4. 処理の流れ ▪ 5. ts.TypeChecker の展望 3
1. 推論内容が見える「ts.TypeChecker」
1. 推論内容が見える「ts.TypeChecker」 昨年末の Advent Calendar 投稿ネタとして、 ts.TypeChecker を使った「anycop」 というツールを作りました。 anycop:
https://www.npmjs.com/package/anycop Qiita記事:https://qiita.com/Takepepe/items/3353159894ed57b6f0a8
1. 推論内容が見える「ts.TypeChecker」 これは CLI で、any推論となっている 宣言箇所を洗い出す代物です。 プロジェクト全体の型安全カバレッジを算出し、 CIを利用したワークフローに導入できます。 anycop: https://www.npmjs.com/package/anycop
Qiita記事:https://qiita.com/Takepepe/items/3353159894ed57b6f0a8
1. 推論内容が見える「ts.TypeChecker」 ツール内部で TypeScript Compiler API を使っていて、 ts.TypeChecker がこの機能の実現に貢献しました。 ts.TypeChecker
は 「VSCodeにおけるDXの勘所をNode.js で享受できるAPI」 と言っても過言ではないでしょう。
1. 推論内容が見える「ts.TypeChecker」 「マウスオーバーしたら、推論内容が見えるアレ」を、 Node.js アプリケーションに落とし込むことが出来る と想像すると分かりやすいです。 どの様に扱うのか、 まずは可視化されたものを見ていきます。
2. TypeScript AST Viewer が教えてくれること
2. TypeScript AST Viewer が教えてくれること TypeScript AST Viewer は TSCompilerAPI
を扱う上で必携ツールですね。
2. TypeScript AST Viewer が教えてくれること 興味対象の ts.Node がどの様に表現されているのか分かります。
2. TypeScript AST Viewer が教えてくれること 例えば「const flag = false」という 変数宣言があった場合。
この変数に適用されている型推論は TreeViewer(画面中央) の VariableDeclaration を 選択ことすることで調べられます。
2. TypeScript AST Viewer が教えてくれること PropertiesViewer(画面右)に 表示されている「Type」の内訳を確認すると 「flags:512 (BooleanLiteral)」が 適用されていることが確認できます。
2. TypeScript AST Viewer が教えてくれること この 512 という値は、ts.TypeFlags の enum
に格納されている列挙値です。 そこには、object 以外に判別できる 数種類の型が列挙されています。 参照:https://github.com/microsoft/TypeScript/blob/master/lib/typescript.d.ts#L2334-L2382
2. TypeScript AST Viewer が教えてくれること ts.TypeChecker のすごさは、 興味対象 ts.Node の型推論を拾えることです。
PropertiesViewer の「Type」に表示されている内容は、 ts.TypeChecker で拾える内容そのものです。
ifStatememt で絞り込まれた値「n」も、 「推論内容を絞り込んだ状態で」取得できます。
2. TypeScript AST Viewer が教えてくれること 興味対象の ts.Node がどの様なものであるのか把握し、 Node.js でどの様に取り扱うのか?
アイディア次第でこれまでの linter では不可能だった規制を 実現することが出来ます。 ワクワクしてきましたね。
3. 正規表現で命名を規制する
3. 正規表現で命名を規制する この API を使い「wordcop」というツールを作りました。 次の三点を突合し、望まない命名を機械的に弾きます。 ▪ 変数名名称 ▪ 推論適用されている型
▪ 正規表現 https://www.npmjs.com/package/wordcop
3. 正規表現で命名を規制する 「boolean / number / string / array」 のいずれかが推論適用されている
変数を見つけた場合、正規表現による チェックが走ります。 module.exports = { targetDir: "../example-app", regExpChecker: { boolean: /^(is|has|should)/i, number: /.*(count|size|length)$/i, string: /.*(label|str)$/i, array: /.*(s|es|ies|list|items)$/i } }
3. 正規表現で命名を規制する 「チェック対象としたい型が適用された変 数」に対し、正規表現を必要なだけコン フィグファイルに記述します。 (正規表現サンプルがイケてないのは容赦ください) module.exports = { targetDir:
"../example-app", regExpChecker: { boolean: /^(is|has|should)/i, number: /.*(count|size|length)$/i, string: /.*(label|str)$/i, array: /.*(s|es|ies|list|items)$/i } }
3. 正規表現で命名を規制する npm に上がってるので試してみて貰えると嬉しいです。 $ yarn add -D wordcop
4. 処理の流れ
4. 処理の流れ ▪ 1. ts.TypeChecker を取得する ▪ 2. ts.TypeFlags に対応する正規表現規制をマッピングする
▪ 3. ts.SourceFile 毎にトラバース ▪ 4. ts.VariableDeclaration 毎にチェック
4-1. ts.TypeChecker を取得する はじめに ts.TypeChecker を取得します。 ts.TypeChecker は ts.Program から取得することができる
ts.Program と対のインスタンスです。 const checker: ts.TypeChecker = program.getTypeChecker()
4-1. ts.TypeChecker を取得する エントリーポイントで生成した ts.TypeChecker インスタンス(checker)を アプリケーション内で引き回します。 const checker: ts.TypeChecker
= program.getTypeChecker()
4-2. ts.TypeFlags に対応する正規表現規制をマッピングする 全ての変数宣言 Node に処理を 試みるので、変数宣言 Node に 対応する正規表現規制マッピン
グをあらかじめ用意します。 export const getTypeRegExpChecker = ( regExpChecker: RegExpChecker ): TypeRegExpChecker => ({ [ts.TypeFlags.Object]: (identifier, isArrayTypeNode) => { if (!isArrayTypeNode) return false const res = identifier.match(regExpChecker.array) if (res) return false return ` ${regExpChecker.array}` }, [ts.TypeFlags.Boolean]: regExpChecker.boolean, [ts.TypeFlags.Number]: regExpChecker.number, [ts.TypeFlags.String]: regExpChecker.string, [ts.TypeFlags.BooleanLiteral]: regExpChecker.boolean, [ts.TypeFlags.NumberLiteral]: regExpChecker.number, [ts.TypeFlags.StringLiteral]: regExpChecker.string })
4-2. ts.TypeFlags に対応する正規表現規制をマッピングする BooleanLiteral 推論と、Boolean 推論は TypeFlags の 種類が異なります。 いずれも同じ扱いとしたい
ツールの利便上から、 内部マッピングで対応します。 export const getTypeRegExpChecker = ( regExpChecker: RegExpChecker ): TypeRegExpChecker => ({ [ts.TypeFlags.Object]: (identifier, isArrayTypeNode) => { if (!isArrayTypeNode) return false const res = identifier.match(regExpChecker.array) if (res) return false return ` ${regExpChecker.array}` }, [ts.TypeFlags.Boolean]: regExpChecker.boolean, [ts.TypeFlags.Number]: regExpChecker.number, [ts.TypeFlags.String]: regExpChecker.string, [ts.TypeFlags.BooleanLiteral]: regExpChecker.boolean, [ts.TypeFlags.NumberLiteral]: regExpChecker.number, [ts.TypeFlags.StringLiteral]: regExpChecker.string })
4-2. ts.TypeFlags に対応する正規表現規制をマッピングする この正規表現規制マッピングは、 コンフィグファイルで 上書きできる様に設計しています。 export const getTypeRegExpChecker =
( regExpChecker: RegExpChecker ): TypeRegExpChecker => ({ [ts.TypeFlags.Object]: (identifier, isArrayTypeNode) => { if (!isArrayTypeNode) return false const res = identifier.match(regExpChecker.array) if (res) return false return ` ${regExpChecker.array}` }, [ts.TypeFlags.Boolean]: regExpChecker.boolean, [ts.TypeFlags.Number]: regExpChecker.number, [ts.TypeFlags.String]: regExpChecker.string, [ts.TypeFlags.BooleanLiteral]: regExpChecker.boolean, [ts.TypeFlags.NumberLiteral]: regExpChecker.number, [ts.TypeFlags.StringLiteral]: regExpChecker.string })
4-2. ts.TypeFlags に対応する正規表現規制をマッピングする 「array」の扱いは一工夫必要です。配列推論されている値は、 ts.TypeFlags 上では「ts.TypeFlags.Object」として扱われます。 これは、ts.Type に含まれる symbol.name を調べることで
'Array' という文字列を取得できるので、これを判断材料としています。 const { flags, symbol }: ts.Type = checker.getTypeAtLocation(node) const isArrayTypeNode = symbol.name === 'Array'
4-2. ts.TypeFlags に対応する正規表現規制をマッピングする ts.TypeFlags.Object は、Array や Object を表すので、 単純に正規表現だけでなく、判定関数を実行します。 [ts.TypeFlags.Object]:
(identifier, isArrayTypeNode) => { if (!isArrayTypeNode) return false const res = identifier.match(regExpChecker.array) if (res) return false return ` ${regExpChecker.array}` }
4-2. ts.TypeFlags に対応する正規表現規制をマッピングする false を返すものは違反していない ts.Node と判断。 違反がある場合は与えられた正規表現規制を、 期待値としてエラー文字列出力します。 [ts.TypeFlags.Object]:
(identifier, isArrayTypeNode) => { if (!isArrayTypeNode) return false const res = identifier.match(regExpChecker.array) if (res) return false return ` ${regExpChecker.array}` }
4-2. ts.TypeFlags に対応する正規表現規制をマッピングする この判定関数をコンフィグに公開することで、 更に自由に(詳細に)ルールを書くことも 出来るでしょう。 [ts.TypeFlags.Object]: (identifier, isArrayTypeNode) =>
{ if (!isArrayTypeNode) return false const res = identifier.match(regExpChecker.array) if (res) return false return ` ${regExpChecker.array}` }
4-3. ts.SourceFile 毎にトラバース ファイル単位(ts.SourceFile 単位)で実行する、トラバース関数です。 switch (node.kind) { case ts.SyntaxKind.VariableDeclaration:
if (ts.isVariableDeclaration(node)) { const erorrMessage = checkNode(checker, typeRegExpChecker, node) if (erorrMessage) { const diagnostic = getDiagnostic(source, node, erorrMessage) console.log(diagnostic) diagnostics.push(diagnostic) } } break }
4-3. ts.SourceFile 毎にトラバース node が ts.VariableDeclaration の場合、checkNode 関数を実行します。 switch (node.kind)
{ case ts.SyntaxKind.VariableDeclaration: if (ts.isVariableDeclaration(node)) { const erorrMessage = checkNode(checker, typeRegExpChecker, node) if (erorrMessage) { const diagnostic = getDiagnostic(source, node, erorrMessage) console.log(diagnostic) diagnostics.push(diagnostic) } } break }
4-4. ts.VariableDeclaration 毎にチェック checkNode 関数で typeRegExpChecker と 突合します。 ts.Type の取得
正規表現規制の取得 変数名の取得 export function checkNode<T extends ts.VariableDeclaration>( checker: ts.TypeChecker, typeRegExpChecker: TypeRegExpChecker, node: T ) { const type = checker.getTypeAtLocation(node) const { flags, symbol } = type const check = typeRegExpChecker[flags] if (!check) return false const identifier = node.name.getText() if (typeof check === 'function') { const isArrayTypeNode = symbol.name === 'Array' return check(identifier, isArrayTypeNode, node) } return checkByRegExp(identifier, check) }
4-4. ts.VariableDeclaration 毎にチェック checkNode 関数で typeRegExpChecker と 突合します。 ts.Type の取得
正規表現規制の取得 変数名の取得 export function checkNode<T extends ts.VariableDeclaration>( checker: ts.TypeChecker, typeRegExpChecker: TypeRegExpChecker, node: T ) { const type = checker.getTypeAtLocation(node) const { flags, symbol } = type const check = typeRegExpChecker[flags] if (!check) return false const identifier = node.name.getText() if (typeof check === 'function') { const isArrayTypeNode = symbol.name === 'Array' return check(identifier, isArrayTypeNode, node) } return checkByRegExp(identifier, check) }
4-4. ts.VariableDeclaration 毎にチェック checkNode 関数で typeRegExpChecker と 突合します。 ts.Type の取得
正規表現規制の取得 変数名の取得 export function checkNode<T extends ts.VariableDeclaration>( checker: ts.TypeChecker, typeRegExpChecker: TypeRegExpChecker, node: T ) { const type = checker.getTypeAtLocation(node) const { flags, symbol } = type const check = typeRegExpChecker[flags] if (!check) return false const identifier = node.name.getText() if (typeof check === 'function') { const isArrayTypeNode = symbol.name === 'Array' return check(identifier, isArrayTypeNode, node) } return checkByRegExp(identifier, check) }
4-4. ts.VariableDeclaration 毎にチェック checkNode 関数で typeRegExpChecker と 突合します。 ts.Type の取得
正規表現規制の取得 変数名の取得 export function checkNode<T extends ts.VariableDeclaration>( checker: ts.TypeChecker, typeRegExpChecker: TypeRegExpChecker, node: T ) { const type = checker.getTypeAtLocation(node) const { flags, symbol } = type const check = typeRegExpChecker[flags] if (!check) return false const identifier = node.name.getText() if (typeof check === 'function') { const isArrayTypeNode = symbol.name === 'Array' return check(identifier, isArrayTypeNode, node) } return checkByRegExp(identifier, check) }
4-4. ts.VariableDeclaration 毎にチェック checkNode 関数で typeRegExpChecker と 突合します。 ts.Type の取得
正規表現規制の取得 変数名の取得 export function checkNode<T extends ts.VariableDeclaration>( checker: ts.TypeChecker, typeRegExpChecker: TypeRegExpChecker, node: T ) { const type = checker.getTypeAtLocation(node) const { flags, symbol } = type const check = typeRegExpChecker[flags] if (!check) return false const identifier = node.name.getText() if (typeof check === 'function') { const isArrayTypeNode = symbol.name === 'Array' return check(identifier, isArrayTypeNode, node) } return checkByRegExp(identifier, check) }
5. ts.TypeChecker の展望
5. ts.TypeChecker の展望 今回のツールは、ガイドラインに準拠するためのサポートツールでしたが、 その展望は様々です。 JavaScript の記述として誤りではないものの、 その潜在的なリスクから特定の記述を弾きたい場合に有用です。
5. ts.TypeChecker の展望 例えば「条件分岐には真偽値しか許容しない」 といった「特定構文 + 特定型」の規制も出来るでしょう。 ▪ 文字列を条件分岐に指定してしまった ▪
数値を条件分岐に指定してしまった これらの要因に起因する事故は、機械的に防ぐことが出来そうです。
5. ts.TypeChecker の展望 ts.TypeChecker を利用することで、 AST のメタ情報を超えた、 より強力な linter が期待できます。
これは、型システムを持つ TypeScript にしか 出来ないことなので、積極的に活用していきたいですね。
ご静聴ありがとうございました