代码分析工具 自定义插件——检查any类型的代码

43 阅读2分钟

代码分析工具 自定义插件——检查any类型的代码

本文是基于掘金小册《前端依赖治理:代码分析工具开发实战》

gitHub仓库:github.com/huang-jaskl…

逻辑步骤

源代码过于庞大,以下步骤只提取主要代码

扫描文件

我们首先需要在analysis.config.js文件中进行配置需要扫描的文件路径

image.png 我们会根据不同的文件类型,扫描不同的文件:

// sitem 为文件目录
if (type === CODEFILETYPE.VUE) {
  tempEntry = scanFileVue(sitem);
} else if (type === CODEFILETYPE.TS) {
  tempEntry = scanFileTs(sitem);
}

在扫描文件过程中,我们使用到了glob依赖进行扫描

示例:该文件目录下所有tstsx文件都会被获取到,不论中间文件层级

// 扫描TS文件
function scanFileTs(scanPath) {
  const tsFiles = glob.sync(path.join(process.cwd(), `${scanPath}/**/*.ts`));
  const tsxFiles = glob.sync(path.join(process.cwd(), `${scanPath}/**/*.tsx`));
  return tsFiles.concat(tsxFiles);
}

解析代码生成

// 解析ts文件代码,获取ast,checker
exports.parseTS = function (fileName) {
  // 创建Program
  // fileNames参数表示文件路径列表,是一个数组,可以只传1个文件
  // options参数是编译选项,可以理解成tsconfig
  const program = tsCompiler.createProgram([fileName], {});
  const ast = program.getSourceFile(fileName);
  const checker = program.getTypeChecker();
  return {
    ast,
    checker,
  };
};

处理AST树

首先,我们需要分析any类型的特征:

image.png

node.kind === tsCompiler.SyntaxKind.AnyKeyword // 节点的类型为AnyKeyword

然后我们需要获取any类型所在的代码,获取到代码块:

我们调用了上下文(CodeAnalysis)中的_getFullCode方法,进行获取

let fullCode = context._getFullCode(tsCompiler, node);

具体实现:

image.png

  _getFullCode(tsCompiler, node) {
    if (node.parent && !tsCompiler.isSourceFile(node.parent)) {
      return this._getFullCode(tsCompiler, node.parent);
    } else {
      return node.getFullText();
    }
  }

逐级向上查找,如果该节点的父节点是SourceFile类型,那么该节点已是代码块的根节点(除了File节点),我们可以通过getFullText()方法进行获取

获取节点行数

const line =ast.getLineAndCharacterOfPosition(node.getStart()).line + baseLine + 1; // 获取节点所在行

完整代码

exports.anyTypePlugin = function (analysisContext) {
  const mapName = "anyTypeMap";
  // 在分析实例上下文挂载副作用
  analysisContext[mapName] = {};

  function isAnyTypeCheck(
    context,
    tsCompiler,
    checker,
    node,
    depth,
    apiName,
    filePath,
    projectName,
    httpRepo,
    line
  ) {
    try {
      if (node.kind === tsCompiler.SyntaxKind.AnyKeyword) {
        let fullCode = context._getFullCode(tsCompiler, node);
        if (!context[mapName][filePath]) {
          // 记录信息
          context[mapName][filePath] = [];
          let temp = {};
          temp.code = fullCode;
          temp.line = line;
          temp.filePath = filePath;
          temp.pos = node.pos;
          temp.end = node.end;
          context[mapName][filePath].push(temp);
        } else {
          let temp = {};
          temp.code = fullCode;
          temp.line = line;
          temp.filePath = filePath;
          temp.pos = node.pos;
          temp.end = node.end;
          context[mapName][filePath].push(temp);
        }
      }
    } catch (e) {
      const info = {
        projectName: projectName,
        apiName: apiName,
        httpRepo: httpRepo + filePath.split("&")[1] + "#L" + line,
        file: filePath.split("&")[1],
        line: line,
        stack: e.stack,
      };
      context.addDiagnosisInfo(info);
      return false;
    }
  }
  return false;
};

return {
  mapName: mapName,
  checkFun: isAnyTypeCheck,
  afterHook: null,
};

不足

当前的插件只能检查类型中明显存在any的情况,比如Array<any>any之类的

但是对于复杂类型来说是检查不到的,比如以下情况:

interface IProps {
    name:any,
    age:number
}

let info : IProps = {
    name:1,
    age:12
}