编写自定义TSLint Rules

在使用TypeScript编写前端代码时,使用TSLint规范化代码是一个不错的选择。在必要的时候,除了常见的一些规则,还可以自己根据团队需要编写自定义的代码检查规则,这里以实例讲解如何编写一个TSLint Rule并应用。

基本要求

TSLint Rule的代码文件名、内容等都有一些特殊的格式要求,如下:

  • Rule名称命名必须是短横线式(kebab-cased),e.g. tsx-no-any-props

  • Rule文件的命名必须是驼峰式(camel-cased),e.g. tsxNoAnyProps

  • Rule文件的命名必须以Rule为后缀,e.g. tsxNoAnyPropsRule.ts

  • 导出的类名必须是Rule且继承Lint.Rules.AbstractRule

实例

现在我们需要定制一个规则,在React TypeScript代码中,不允许设置any类型的props接口,这个规则的名称为tsx-no-any-props,则文件命名为tsxNoAnyPropsRule.ts

代码示例:

import * as ts from "typescript";  
import * as Lint from "tslint";

export class Rule extends Lint.Rules.AbstractRule {  
    // tslint:disable object-literal-sort-keys
    public static metadata: Lint.IRuleMetadata = {
        ruleName: "tsx-no-any-props",
        description: "Forbidden the usage of `any` props in typescript-react component",
        optionsDescription: "Not configurable.",
        options: null,
        optionExamples: ["true"],
        type: "functionality",
        typescriptOnly: true
    };
    // tslint:enable object-literal-sort-keys

    public static FAILURE_STRING = "Props of React component should not be any";

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

class NoAnyPropsWalker extends Lint.RuleWalker {  
    public visitClassDeclaration(node: ts.ClassDeclaration) {
        node.heritageClauses.forEach(({ types }) => {
            types.forEach(({ expression, typeArguments }) => {
                const expressionTxt = expression.getText();
                if (
                    Array.isArray(typeArguments) &&
                    typeArguments.length > 0 &&
                    [
                        "React.Component",
                        "React.PureComponent",
                        "Component",
                        "PureComponent"
                    ].includes(expressionTxt)
                ) {
                    // if props is any type
                    const propsNode: ts.TypeNode = typeArguments[0];
                    if (
                        propsNode !== undefined &&
                        propsNode !== null &&
                        propsNode.kind === ts.SyntaxKind.AnyKeyword
                    ) {
                        this.addFailure(
                            this.createFailure(
                                propsNode.getStart(),
                                propsNode.getWidth(),
                                Rule.FAILURE_STRING
                            )
                        );
                    }
                }
            });
        });

        super.visitClassDeclaration(node);
    }
}

整个过程主要是,TSLint核心给我们提供了当前应用的代码的AST,那么规则对AST中各个Node类型,参数等做校验,不通过规则则抛出失败。

使用

使用TypeScript编写的规则文件,首先必须编译成JavaScript,可以将tsc的编译目标设置为es2015

有两种方式引入自定义的规则:

  • 指定路径 - 在.tslintrc.json或类型配置文件中,rulesDirectory项新增自定义规则的路径
  • 规则扩展 - 将规则发布为npm package,并在package.jsonmain入口指定参数JSON文件,如main参数的值设定为tslint-custom.json,则该文件内容类似如下:
{
    "rulesDirectory": "./lib/rules",
    "rules": {
        "tsx-no-any-props": true,
        "tsx-no-any-state": true
    }
}

想应用该规则的项目.tslintrc.json或同等配置文件中,应用如下:

{
  "extends": ["tslint:latest", "custom-tslint-rules-collection"],
  "rules": {
    // override custom-tslint-rules rules here
    "tsx-no-any-props": false
  }
}

测试

良好的功能测试是不可或缺的,这里利用mochats-mocha库进行单元测试。其中ts-mocha可以让我直接编写TypeScript格式的测试文档且无需编译。这一部分如何做可直接参考实际项目CustomTSLintRules