1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Promiseを返す関数がawaitかcatch()されてなかったらエラーにするESLint rule

Last updated at Posted at 2024-12-07

個人的にはUnhandled Promise Rejectionでnodeプロセスを終了させたくない場合は
process.on('unhandledRejection') イベントをハンドルすると良いと思う。

それとは別に、意図しない処理を洗い出すのに役立つのでUnhandledなPromiseがあったら見つけるESLintルールを用意してみた。

const ts = require('typescript');
module.exports = {
  meta: {
    type: "problem",
    docs: {
      description: "Ensure promises are handled with `await` or `catch`",
      category: "Possible Errors",
      recommended: true,
    },
    schema: [],
    messages: {
      unhandledPromise:
        "Promises must be handled with `await` or `.catch()`.",
    },
  },
  create(context) {
    const tc = context.parserServices.program.getTypeChecker();
    function isTypePromise(t, sym, visited = new Set()) {
      if (visited.has(t)) return false;
      visited.add(t);
      const symbol = t.getSymbol();
      if (symbol === sym) return true;
      if (t.isUnionOrIntersection()) {
        return t.types.some(subType =>
          isTypePromise(subType, sym, visited));
      }
      const baseTypes = t.getBaseTypes?.();
      if (baseTypes && baseTypes.some(base =>
        isTypePromise(base, sym, visited))) {
        return true;
      }
      const typeArgs = t.typeArguments;
      if (typeArgs && typeArgs.some(arg =>
        isTypePromise(arg, sym, visited))) {
        return true;
      }
      return false;
    }
    function isPromise(node) {
      const tsNode = context.parserServices.esTreeNodeToTSNodeMap.get(node);
      const type = tc.getTypeAtLocation(tsNode);
      const promiseSymbol = tc.resolveName("Promise",
        tsNode.getSourceFile(), ts.SymbolFlags.Interface, false);
      if (!promiseSymbol) {
        return false;
      }
      return isTypePromise(type, promiseSymbol);
    }
    function hasCatchInChain(callNode) {
      let currentCall = callNode;
      while (
        currentCall &&
        currentCall.type === "CallExpression" &&
        currentCall.callee.type === "MemberExpression"
      ) {
        const methodName = currentCall.callee.property.name;
        if (methodName === "catch") return true;
        if (currentCall.callee.object.type === "CallExpression") {
          currentCall = currentCall.callee.object;
        } else {
          break;
        }
      }
      return false;
    }
    return {
      ExpressionStatement(node) {
        const { expression } = node;
        if (
          expression.type === "CallExpression" &&
          context.parserServices &&
          context.parserServices.program
        ) {
          if (isPromise(expression)) {
            if (node.parent && node.parent.type === "AwaitExpression") {
              return;
            }
            if (!hasCatchInChain(expression)) {
              context.report({
                node,
                messageId: "unhandledPromise",
              });
            }
          }
        }
      },
    };
  },
},
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?