Dart-自定义Lint之路(四)-实现基于类型识别的Lint规则

2,029 阅读3分钟
  1. # Dart-自定义Lint之路(一)-创建Analyzer Plugin
  2. # Dart-自定义Lint之路(二)-运行和调试Analyzer Plugin
  3. # Dart-自定义Lint之路(三)-实现简单的Lint规则

上个文档中,已经实现了禁止xx为前缀的变量命名的lint规则。这种规则仅和名称有关,所以实现起来非常简单。而在Flutter代码编写时,会发现一些诸如list.length > 0被提示使用list.isNotEmpty来代替的lint规则,而你如果自定义的某个结构定义了length,然后也这么写,却并不会有提示,说明这条lint规则是针对于List、Set、Map类型的,也就说明lint规则的实现中,是能检测类型的。

那么这篇文章,介绍如何去实现一个基于类型判定的lint规则——禁止Widget嵌套过深

预期效果

Container(
    child: Container(
        child: Container(
            child: Container(),
        ),
    ),
);

当连续嵌套Widget超过3层时,第四层Widget进行报错提示。

类型检测

如何在Analyzer Plugin中检测类型?既然官方的lint可以做到,那么就直接去看官方lint中的实现:src/rules。里面关于类型判定的dart文件主要有5个:

  • analyzer.dart

  • ast.dart

  • dart_type_utilities.dart

  • flutter_utils.dart

  • utils.dart

这5个都是工具类性质的,所以可以直接拷贝到项目中直接使用。

flutter_utils.dart中就有数个方法判定是否是Widget、StatelessWidget、StatefulWidget等等。所以直接使用就可以了。

连续嵌套检测

搞定了Widget的类型判定,那么最后只需要检测Widget嵌套了多少层就可以了。

首先确定检查节点:

  1. 使用InstanceCreationExpression的节点。因为Widget的嵌套,实际上是不停的进行实例创建。

  2. 类型得是Widget。不是Widget也没必要去查。

  3. 是叶子Widget,自身没有child或children。

接着我们在代码中找到这个节点,1和2非常好实现:

class WidgetDepthVisitor extends GeneralizingAstVisitor {
  @override
  visitInstanceCreationExpression(InstanceCreationExpression node) {
    if (isWidgetType(node.staticType)) {  //isWidgetType是flutter_utils中的顶层方法,直接使用即可
        //TODO
    }
    return super.visitInstanceCreationExpression(node);
  }
}

第3点,需要检查当前这个Widget的创建Ast语法树中,是否有child或者children参数:

  ///检查是否有child或children或body或home
  bool hasChildOrChildren(ArgumentList node) {
    for (var arg in node.arguments) {
      var name = (arg as NamedExpression).name.label.name;
      if (name == 'child' || name == 'body' || name == 'home') {
        if (arg.childEntities.any((element) => element is NullLiteral)) {
          return false;
        }
        return true;
      } else if (name == 'children') {
        ListLiteral? list;
        try {
          list = arg.childEntities
              .firstWhere((element) => element is ListLiteral) as ListLiteral;
          if (list.toSource() == '[]') {
            return false;
          }
        } catch (_) {}
        return true;
      }
    }
    return false;
  }

这样就能找到叶子Widget节点,然后就是回溯向上检测嵌套层数,实现逻辑也比较简单,就是连续不停向上检查node是否是InstanceCreationExpression然后又是Widget类型的。

  int getContinuousWidgetDepths(AstNode? node) {
    int depth = 0;
    while (node != null && node.parent != null) {
      if (node is InstanceCreationExpression) {
        if (isWidgetType(node.staticType)) {
          depth++;
        }
      }
      node = node.parent;
    }
    return depth;
  }

获取到Depths后,根据规则,超过3个,则加入issues中,后面交给Plugin进行报错处理即可:

  @override
  visitInstanceCreationExpression(InstanceCreationExpression node) {
    if (isWidgetType(node.staticType)) {
      //如果是Widget的类型,那么检查是否是叶子结点
      bool leafNode = !hasChildOrChildren(node.argumentList);
      if (leafNode) {
        //如果是叶子Widget,那么检查嵌套的Widget深度
        int widgetDepth = getContinuousWidgetDepths(node);
        if (widgetDepth > 3) {
          //mirrorLog.info('存在Widget深度超过3');
          issues.add(Issue(node.offset, node.length));
        }
      }
    }
    return super.visitInstanceCreationExpression(node);
  }

效果展示

image.png