ESLint からいっぱい怒られた話

C/C++ 使いが JavaScript を実装すると陥りやすい ESLint で怒られる実装を展開するページです。
2023.06.21

こんにちは、高崎@アノテーション です。

はじめに

ESLint は JavaScript/TypeScript の静的コード分析ツールですが、

  • 事前に問題のある実装を検知してバグを回避する
  • 複数人で構成するチームで開発する場合にコードの一貫性を維持する

…といった目的で使われます。

今回、C/C++ 使いの筆者が TypeScript のコードを組んだ際に C/C++ 風に組んでしまったがためにハマってしまった ESLint の解析結果を展開するとともに、その対処で調べたことを記事にしたいと思います。

前提

以下が基本条件となります。

  • eslint を導入し airbnb 規約を追加している
  • コメントに eslint-disable xxxxx と書いて蓋をしない

外部参照関数を定義すると怒られた

怒られたコード

C/C++ であればよくある実装方法だと思いますが、他のソースからも関数をコールできるよう実装すると関数宣言の箇所で怒られました。

export function func(argNumber: number): number { // ←ここで怒られる
  何かの処理
}

メッセージ内容と詳細

Expected a function expression

ルールの原文はここ です。

function を定義する方法はいくつかありますが、設定により ESLint がエラーとして報告することがあります。

原文でも it is just a prefrence. とあるように好みの問題でもありますが、ガイドラインをサポートする狙いもあるのでは、という認識です。

関数定義しているソース

// 関数式
export func = function() {
    :
};
// 関数定義 ← デフォルトではこちらをエラーと報告する
export function func2() {
    :
}

本設定はデフォルトで関数式の定義にするように設定されていますが、ESLint の設定ファイル(.eslint.*)に func-style を関数宣言で行うような設定(declarationを有効にする)にすると、関数定義の設定にするよう解析されます。

対応〜どうすれば良いか

1. 関数式で定義する

関数式で定義すると下記のような形になるかと思います。

export const func: number = (argNumber: number) => {
  何かの処理
};

2. .eslintrc.* の定義を見直す

先述の設定ファイルに func-style を関数宣言で行う設定(declaration)にして関数定義側に統一するか、func-style はそれぞれの感性に任せるポリシーでしたら func-style 自体を無効にすることで怒られることは無くなります(ただし、前者は関数式にした実装でエラーが発生します)。

いずれにせよ、プロジェクトやチーム内でのガイドラインを確認・決定してから設定する必要があると思います。

配列へアクセスしようとしたら怒られた

怒られたコード

配列変数へインデックスを指定してアクセスすることは C/C++ でも良く使用される表現ですが、その実装では ESLint だとエラーと言われました。

    :
  const stringPPAP: string = "I have a pen./I have an apple./Uhhh./ApplePen.";
  const arrayPPAP: string[] = stringPPAP.split("/");
  const stringUhhh: string = arrayPPAP[2];    // ←ここで怒られる
    :

メッセージ内容と詳細

Use array destructuring

ルールの原文はここ です。

JavaScript ES6 より 分割代入 という記載が出来るようになり、個別に変数へ取り出すことが可能になりました。

この記事を記載している現在の ESLint のバージョンは 8.42.0 ですが、8.30 の prefer-destructuring を日本語化したページ によると、

ESLint の prefer-destructuring ルールを使用すると(中略)、可読性が向上し、配列やオブジェクト内のデータにアクセスするために必要なコードの量を減らすことができます。

…だそうです。

ちなみに、今回は配列で怒られましたが、戻り値がメンバーを持つオブジェクトを返す関数からそのメンバーを取り出す時にアクセスしても Use array destructuring と怒られます。

    :
  const objPPAP = { pen: "I have a pen.", pineapple: "I have a pineapple.", apple: "I have an apple." };
  const strPen = objPPAP.pen;           // ← ここで怒られる
    :

対応〜どうすれば良いか

いくつか方法があるかと思いますが、例をいくつか。

1. eslint オプションの --fix に委ねる

コマンドラインから自動で ESLint に直してもらうというやり方です。
※他のエラーにおいても公式ドキュメントで下記の表現があれば対象になります。

? Some problems reported by this rule are automatically fixable by the --fix command line option

package.json の scripts 欄に入れてしまうのも手かもしれません。

package.jsonサンプル

    :
  "scripts": {
    :
    "eslint:fix": "eslint --fix .",
    :

2. 分割代入に修正する

配列へダイレクトにアクセスするのではなく、箱として用意しておいてから取得する形です。

    :
  const stringPPAP: string = "I have a pen./I have an apple./Uhhh./ApplePen.";
  const arrayPPAP: string[] = stringPPAP.split("/");
//  const stringUhhh = arrayPPAP[2];    // ←ここで怒られる
  const [, , stringUhhh] = arrayPPAP;
    :

後半の Use array destructuring と怒られた実装は下記のような形での修正になります。

    :
  const objPPAP = { pen: "I have a pen.", pineapple: "I have a pineapple.", apple: "I have an apple." };
//  const strPen = objPPAP.pen;           // ← ここで怒られる
  const { pen } = objPPAP;
    :

for ループを書いたらいろいろ怒られた

怒られたコード

配列に入っているデータを取り出そうとして、 for ループを使って下記のような C/C++ 的記述にすると怒られました。

  const arrayPPAP: string[] = ["Pen", "Pineapple", "Apple", "Pen"];
  for (const index = 0; index = arrayPPAP.length; index++) {    // ← ここで怒られる 1.
    const elementPPAP = arrayPPAP[index];                       // ← ここでも怒られる 2.
    :

エラー内容

怒られた 1.Unary operator '++' used
怒られた 2.Use object destructuring

1つ目は index++index += 1 とすることで解決すると思いますが、2つ目は分割代入を動的に出来そうにないのでこの記載を諦めて C++11 みたく、

  std::vector vec{ 10, 20, 30 };
    :
  for ( int element : vec )
    :

…のような foreach 的な記載が JavaScript にないだろうか、と探すと for-of というのを見つけましたので実装したところ、別の怒られ方をしました。

再び怒られたコード

  const arrayPPAP: string[] = ["Pen", "Pineapple", "Apple", "Pen"];
  for (const arrayElement of arrayPPAP) {    // ←ここで怒られる
     :

メッセージ内容と詳細

iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations

…長いですが、「変換にはランタイムジェネレータが必要だけど、この処理は重すぎるので配列のアクセスをあんじょうやってループを回避してください。」的なメッセージでしょうか。

ルールの原文はこちら ですが、幾つもの実装方法がある中で、問題の起きやすい(であろう)実装を禁止にして事前にある程度安全な実装方法にする主旨のようです。

こちらの参考文献 にも理由と対応についての記載がありましたが、以下対処方法です。

対応〜どうすれば良いか

forEach を使う(break 不要のときに使用可能)

  const arrayPPAP: string[] = ["Pen", "Pineapple", "Apple", "Pen"];
  arrayPPAP.forEach((arrayElement) => {
     :
  });

今回は continue や break する必要が全く無く、全てループを回す必要があったので forEach を使いました。

なお、continue はこの forEach 内の関数を return することで実現出来ますが、途中でループを抜ける break は出来ないようです。

every/some を使う

break を使いたい場合の例です。
先程紹介した参考文献 に記載がありましたが、例えば今回の配列の例で指定した文字列を見つけるまで、であれば、下記のようにします。

  // argString が探したい string 変数とする。
  const arrayPPAP: string[] = ["Pen", "Pineapple", "Apple", "Pen"];
  const isMatch = arrayPPAP.some((arrayElement) => (argString === arrayElement));

終わりに

今回は C/C++ 経験者が JavaScript のプログラムを組む際に陥りやすいであろう ESLint チェック内容について記載しました。

同じような状況になってしまった方の対処の一助になれば幸いです。

参考文献

アノテーション株式会社について

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。
「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。
現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。
少しでもご興味あれば、アノテーション株式会社WEBサイト をご覧ください。