在使用 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

代码示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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,则该文件内容类似如下:
1
2
3
4
5
6
7
{
    "rulesDirectory": "./lib/rules",
    "rules": {
        "tsx-no-any-props": true,
        "tsx-no-any-state": true
    }
}

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

1
2
3
4
5
6
7
{
    "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