TSLint仕様を使用してコードをカスタマイズするRule


TSLint仕様を使用してコードをカスタマイズするRule
TSLintはあなたのコードを規範化することができて、vs codeの上のTSLintプラグインに協力して、あなたのコードの潔癖さを治療することができます.
社内のコードスタイルを統一するために、最近会社もいくつかのRuleを開発して、Prettierに協力してまだエイズではありませんか?私はこのRuleの開発を担当しています.TSlintは国内の資料が少ないし、TSLint自身のドキュメントもカスタムRuleの開発について曖昧なので、いろいろな資料を探したり、ソースコードを見たりすることは避けられません.最初のRuleを書くと、後の開発がスムーズになります.今も開発中なので、暇を見つけてブログを書いて、カスタムRuleを書きたい人たちを助けて、回り道をしないでください.興味があれば、私たちが開発しているMagicSpaceにも足を踏み入れることができます.まだ開発中ですが、Star✨下呗:)
公式の例から見ると
本題に入る.
公式の例もはっきりしているので、まず公式の例を見てみましょう.
import * as ts from "typescript";
import * as Lint from "tslint";

export class Rule extends Lint.Rules.AbstractRule {
    public static FAILURE_STRING = "import statement forbidden";

    public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
        return this.applyWithWalker(new NoImportsWalker(sourceFile, this.getOptions()));
    }
}

// The walker takes care of all the work.
class NoImportsWalker extends Lint.RuleWalker {
    public visitImportDeclaration(node: ts.ImportDeclaration) {
        // create a failure at the current position
        this.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING));

        // call the base version of this visitor to actually parse this node
        super.visitImportDeclaration(node);
    }
}

あなたが書くRuleをnoImportRuleと呼ぶとします.ts、このファイルでは、Ruleという名前のクラスをエクスポートし、継承Rulesを表示する必要があります.AbstraactRuleを実装し、そのapplyメソッドを実装します.applyメソッドには、現在Ruleが適用されているソースファイルであるsourceFileのパラメータがあります.戻り値はRuleFailureであり、TSLintでは、Ruleに一致しないものはFailureとみなされ、この戻り値はFailure配列であり、もちろん、私たちは直接それを返すのではなく、Walkerを返します.
WalkerはRuleの核心部分であり、どこが規定に合わないのか、どこでfailureを加えるべきかを判断する仕事です.NoImportsWalkerを宣言します.ここにはRuleWalkerを継承すると書かれています.RuleWalkerはアクセス者に相当します.visitImportDeclarationなど、ソースファイルにアクセスするすべてのimport宣言文にアクセスできます.パラメータnodeが手に入れたのはimportの式で、NodeはASTのノードを表します(TSLintではルールをチェックする前に、まずあなたのソースコードをASTに変換し、いくつかの方法でASTのノードを抽出します.TSLintではTSUtilというツールを使っています.TSLintがこんなに火をつけていると同時に、TSUtilは数十人のstarしかありません.TSLintは火をつけていないようですね.皆さんがASTのノードをどのように抽出するか興味がないのかもしれません.興味があるのはTSLintでしょう)、あとでこのノードを手に入れることでこのノード上の方法を実行することができます.例えば、getText()でこの点の内容を取得したり、getChildren、getParentでこのノードの親子ノードを取得したりするなど、似たような方法がたくさんあります.
コードのthis.addFailure(this.createFailure(node.getStart(), node.getWidth(), Rule.FAILURE_STRING));は、addFailureを呼び出してエラーを追加する目的です.
このruleの機能はimport文を禁止して、実行して効果を見ますか?
例えばここで勝手にモジュールを導入します
import * as Foo from './foo'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~                [import statement forbidden]

この文はTSLintによって間違って報告されていることがわかります.これはaddFailureの役割です.
WalkerとFunction
簡単なRuleで書きました.このように見るのは簡単ではないでしょうか.
実は普通はカスタマイズのRuleをする時、私達は普通は直接RuleWalkerを継承しないで、AbstractWalkerを継承して、それからそのwalkの方法を実現して、例えば以下は私のMagicSpaceの中の1つのコードの断片です
class ExplicitReturnTypeWalker extends AbstractWalker<undefined> {
  /**    */
  walk(sourceFile: TypeScript.SourceFile): void {
    let cb = (node: TypeScript.Node): void => {
      if (
        (TypeScript.isFunctionDeclaration(node) ||
          TypeScript.isArrowFunction(node) ||
          TypeScript.isFunctionExpression(node) ||
          TypeScript.isMethodDeclaration(node)) &&
        node.type
      ) {
      }
      TypeScript.forEachChild(node, cb);
    };

    TypeScript.forEachChild(sourceFile, cb);
  }
}

このwalkerは、関数式、矢印関数、関数宣言文、メソッド宣言など、私が望む関数ノードを取得するために使用されます.TSLintはルールチェックをするときにwalkを呼び出すので、ルール判定要求を達成するために判断ロジックを書くことができます.
それ以外に、ruleが簡単であれば、walkerクラスを作成する必要はありませんが、applyWithFunctionを直接使用することができます.機能的にwalkerと差は多くありません.walkerの代わりに関数を使うだけです.
protected applyWithFunction(sourceFile: ts.SourceFile, walkFn: (ctx: WalkContext<void>) => void): RuleFailure[];
    protected applyWithFunction<T>(sourceFile: ts.SourceFile, walkFn: (ctx: WalkContext) => void, options: NoInfer): RuleFailure[];
    protected applyWithFunction<T, U>(sourceFile: ts.SourceFile, walkFn: (ctx: WalkContext, programOrChecker: U) => void, options: NoInfer, checker: NoInfer): RuleFailure[];

たとえば
applyWithFunction(souceFile, ctx => {
    // some logic
})

この関数はWalkerのwalkメソッドに相当し、ルールをチェックするときにもこの関数を呼び出し、ctxパラメータはWalkerのコンテキストに相当します.addFailureを呼び出す場合は、this.addFailureで呼び出すのではなく、ctx.addFailureで呼び出す.
基本的にwalkerと同じ機能を実現できます.
論理的に比較的簡単なRuleでは、applyWithFunctionの方が軽量になる可能性があります.
Metadata
それ以外に、このRuleを記述するために、Ruleのクラスにメタデータを書くこともできます.私が書いたMetadataを見て
static metadata: IRuleMetadata = {
    ruleName: 'import-groups',
    description: 'Validate that module imports are grouped as expected.',
    optionsDescription: '',
    options: {
      properties: {
        groups: {
          items: {
            properties: {
              name: {
                type: 'string',
              },
              test: {
                type: 'string',
              },
            },
            type: 'object',
          },
          type: 'array',
        },
        ordered: {
          type: 'boolean',
        },
      },
      type: 'object',
    },
    optionExamples: [
      [
        true,
        {
          groups: [
            {name: 'node-core', test: '$node-core'},
            {name: 'node-modules', test: '$node-modules'},
          ],
          ordered: true,
        },
      ],
    ],
    type: 'maintainability',
    hasFix: true,
    typescriptOnly: false,
  };

ここのフィールドの意味はやはりみんながドキュメントを見に来て、ここではもう説明しません.多くのフィールドはオプションで、自分の状況を見て選択する必要があります.具体的には、rule.d.tsのIMetadataインタフェースを参照してください.
対応する式の検索
ファイル全体のimport宣言文を収集したい場合、kindで判断できるように、対応する式を探す必要があります.kindって何?Node.kindは、このノードの意味タイプを表す整数であり、列挙クラスts.SyntaxKindは完全な対応関係を有する.
Oh、実際の例を見てみましょう.
import * as TypeScript form 'typescript'

for (let statement of sourceFile.statements) {
      if (statement.kind === Typescript.SyntaxKind.ImportDeclaration) {
        //...
      }

ここではstatementごとのkindを比較することですべてのimportの宣言文を取得できますが、isImportDeclarationなどの直接呼び出す方法がtsutilにカプセル化されています.
上のコードは
import * as TypeScript form 'typescript'

for (let statement of sourceFile.statements) {
      if (isImportDeclaration(statement)) {
        //...
      }
findImportsの方法で、すべてのimport文を迅速に見つけることができます.
for (const expression of findImports(
      sourceFile,
      ImportKind.AllStaticImports,
    )) {
      //...
    }

便利かどうかはたくさんありますが、もちろん、importだけではありません.
まとめ
TSLintはもちろんTSに必要なスタイルチェックのツールですが、TSLintが持っているいくつかのルールは私たちの奇妙な要求を満たすことができません.それでは、自分でTSLint Ruleを書いて、スタイルの面でコードを規範化するのを助ける必要があります.しばらくはこれらを書くことだけを考えて、国内のTSLint資料は少なすぎて、この博文はただみんなに少しの構想を見つけることを助けて、もちろん、やはりドキュメントとソースコードを結びつけて自分のTSLint規則を構築しなければなりません.