これは TypeScript Advent Calendar 2019 22日目の代打記事です。
前回の投稿が好評だったため、ブラッシュアップ・CLIツール化し、any警察👮♂️を npm publish しました。READMEに記載したとおり、devDependencies に追加し、npx anycop
を実行するとプロジェクトのany診断ができます。
anycop: https://www.npmjs.com/package/anycop
おことわり
これはanyを駆逐せよ、という目的で作ったものではありません。anyの使用状況が可視化されれば、課題点を把握し、チームで共有する事が出来るでしょう。時間のある時、型安全のカバレッジを向上する様な取り組みが出来れば、それはとても素晴らしい事です。any は悪者ではありません。TypeScript にこの仕様がなければ、今の JavaScript エコシステムに静的型付けを適用することは出来なかったでしょう。このツールで、any とより良い関係を築けたら幸いです。
変更点
前回投稿では「変数定義のany推論」に限定しましたが、パワーアップし、次の anyを検知する様になりました。
- 変数宣言の any型推論
- 関数引数の any型推論
- BindingElementの any型推論
- 関数の any戻り型推論
- Arrow関数の any戻り型推論
- AsExpression(as any アサーション)
TypeScriptプロジェクトにインストールし、実行した結果は以下の様になります。
変数宣言の any型推論
変数の明示的なany注釈はもちろん、any 推論も検知します。
function greet(message: any) {
return message
}
// inferred "any" at VariableDeclaration
const message = greet('hello')
関数引数の any型推論
引数の明示的なany注釈はもちろん、参照している型定義が any 相当の場合も検知します。
// annotation of "any" at ParameterDeclaration (message)
function greet(message: any) {
return message
}
BindingElementの any型推論
BindingElementで展開された参照が、any推論されている時も検知します。
type Props = { a: any, b: any }
// inferred "any" at BindingElement (a & b)
function greet({ a, b }: Props) {
return { a, b }
}
関数の any戻り型推論
戻り型の明示的なany注釈はもちろん、any になってしまっている戻り型も検知します。
// inferred "any" at FunctionDeclReturn
function greet() {
const message: any = 'hello'
return message
}
Arrow関数の any戻り型推論
戻り型の明示的なany注釈はもちろん、any になってしまっている戻り型も検知します。
// inferred "any" at ArrowFunctionReturn
const abc = (param: string) => {
switch (param) {
case 'a':
return true
case 'b':
return false
case 'c':
return '' as any
}
}
AsExpression(as any アサーション)
asキーワードによるアサーションを検知します。
function greet() {
return 'hello' as any // at AsExpression
}
anycop.config.js
プロジェクトルートにanycop.config.js
を設置することで、固有の設定ができます。errorThrethold
はエラー報告の閾値です。「TypeSafe Coverage」の合計がこの数値を下回った場合、エラーを発生させることができます。インテグレーションツールへの導入などに活用してください。
module.exports = {
targetDir: ".",
errorThrethold: 0.2
}
検査項目増加による実装の変更点
ts.SourceFile
はts.Node
のルートノードであり、そこを起点に実行する再帰関数(visit関数)を見直しました。ts.Node
はts.SyntaxKind
を持ち合わせており、この SyntaxKind で処理を選り分けています。
function visit(node: ts.Node) {
switch (node.kind) {
case ts.SyntaxKind.VariableDeclaration:
// 変数宣言であれば
// ....処理
break
case ts.SyntaxKind.Parameter:
// 引数であれば
// ....処理
break
case ts.SyntaxKind.BindingElement:
// BindingElementであれば
// ....処理
break
case ts.SyntaxKind.FunctionDeclaration:
// 関数宣言であれば
// ....処理
break
case ts.SyntaxKind.ArrowFunction:
// アロー関数宣言であれば
// ....処理
break
case ts.SyntaxKind.AsExpression:
// アサーションであれば
// ....処理
break
}
ts.forEachChild(node, visit)
}
visit(source)
ts.TypeChecker.getReturnTypeOfSignature
「うっかり関数戻り型がanyに落ちていた…」という検査を追加しました。ts.SignatureDeclaration
は、関数宣言やアロー関数を指します。ts.TypeChecker
に備わったgetSignatureFromDeclaration
関数とgetReturnTypeOfSignature
関数を利用することで、flags
を得ることができます。これをもってts.TypeFlags.Any
と一致しているかを判定し、Anyか否かを判定しています。
// ts.ArrowFunction / ts.FunctionDeclaration は
// ts.SignatureDeclaration のサブタイプ
// node: ts.SignatureDeclaration
const signature = checker.getSignatureFromDeclaration(node)
if (signature) {
const { flags } = checker.getReturnTypeOfSignature(signature)
return checkAny(source, node, flags, name)
}