- Regular expression & Type - Naming Rule Linter

- Regular expression & Type - Naming Rule Linter

#tsc_api_study #1

正規表現と型推論を突合し、命名規則にルールを導入するツールを紹介

5d9cd19df0e91caac118b793b4f803d5?s=128

Takepepe

March 04, 2020
Tweet

Transcript

  1. 2.

    About Me ▪ Takefumi Yoshii / @Takepepe ▪ DeNA /

    DeSC Healthcare ▪ Frontend Developer 2
  2. 3.

    Agenda ▪ 1. 推論内容が見える「ts.TypeChecker」 ▪ 2. TypeScript AST Viewer が教えてくれること

    ▪ 3. 正規表現で命名を規制する ▪ 4. 処理の流れ ▪ 5. ts.TypeChecker の展望 3
  3. 12.

    2. TypeScript AST Viewer が教えてくれること 例えば「const flag = false」という 変数宣言があった場合。

    この変数に適用されている型推論は TreeViewer(画面中央) の VariableDeclaration を 選択ことすることで調べられます。
  4. 14.

    2. TypeScript AST Viewer が教えてくれること この 512 という値は、ts.TypeFlags の enum

    に格納されている列挙値です。 そこには、object 以外に判別できる 数種類の型が列挙されています。 参照:https://github.com/microsoft/TypeScript/blob/master/lib/typescript.d.ts#L2334-L2382
  5. 15.

    2. TypeScript AST Viewer が教えてくれること ts.TypeChecker のすごさは、 興味対象 ts.Node の型推論を拾えることです。

    PropertiesViewer の「Type」に表示されている内容は、 ts.TypeChecker で拾える内容そのものです。
  6. 17.

    2. TypeScript AST Viewer が教えてくれること 興味対象の ts.Node がどの様なものであるのか把握し、 Node.js でどの様に取り扱うのか?

    アイディア次第でこれまでの linter では不可能だった規制を 実現することが出来ます。 ワクワクしてきましたね。
  7. 20.

    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 } }
  8. 24.

    4. 処理の流れ ▪ 1. ts.TypeChecker を取得する ▪ 2. ts.TypeFlags に対応する正規表現規制をマッピングする

    ▪ 3. ts.SourceFile 毎にトラバース ▪ 4. ts.VariableDeclaration 毎にチェック
  9. 25.

    4-1. ts.TypeChecker を取得する はじめに ts.TypeChecker を取得します。 ts.TypeChecker は ts.Program から取得することができる

    ts.Program と対のインスタンスです。 const checker: ts.TypeChecker = program.getTypeChecker()
  10. 27.

    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 })
  11. 28.

    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 })
  12. 29.

    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 })
  13. 30.

    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'
  14. 31.

    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}` }
  15. 34.

    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 }
  16. 35.

    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 }
  17. 36.

    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) }
  18. 37.

    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) }
  19. 38.

    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) }
  20. 39.

    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) }
  21. 40.

    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) }
  22. 44.

    5. ts.TypeChecker の展望 ts.TypeChecker を利用することで、 AST のメタ情報を超えた、 より強力な linter が期待できます。

    これは、型システムを持つ TypeScript にしか 出来ないことなので、積極的に活用していきたいですね。