1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

keitamaxAdvent Calendar 2024

Day 1

ESLintのプラグインを作成する

Last updated at Posted at 2024-12-03

はじめに

こんにちは、エンジニアのkeitaMaxです。

ESLintのカスタムプラグインを作成します。

作りたいもの

作りたいものは、mapのなかでpushをしないことというルールを作ろうと思います。

const a = [1,2,3]
const b = []
a.map(i => b.push(i))

上記のような実装の時にエラーがでるようなESLintのルールを作成していこうと思います。

作成

以下のようなフォルダ構成にしました。

eslint-plugin-hitasura-system-develop
├─ src
│  ├─ index.ts
│  └─ rules
│     └─ notUsePushInMap.ts
├─ tsconfig.json
└─ package.json

srcの中が実際のコードです。

index.ts
import { notUsePushInMapRule } from "./rules/notUsePushInMap";

export const rules = {
  "no-use-push-in-map": notUsePushInMapRule
};

rulesを追加しています。

notUsePushInMap.ts
import { TSESTree } from '@typescript-eslint/utils'
import { RuleModule } from '@typescript-eslint/utils/dist/eslint-utils'

export const notUsePushInMapRule: RuleModule<'notUsePushInMap'> = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'Disallow the use of push inside map method',
    },
    messages: {
      notUsePushInMap: "Do not use push inside a map method."
    },
    schema: []
  },
  defaultOptions: [],
  create(context) {
    return {
      CallExpression(node: TSESTree.CallExpression) {
        const callee = node.callee
        if (callee.type !== 'MemberExpression' ||
          callee.property.type !== 'Identifier' ||
          callee.property.name !== 'map') {
          // mapを使用していない場合はチェックしない
          return
        }

        const callback = node.arguments[0]
        if (!callback || (callback.type !== 'FunctionExpression' && callback.type !== 'ArrowFunctionExpression')) {
          // .map() か .map(function()) の場合でないときはチェックしない
          return
        }
        const body = callback.body

        // 実際にPushを使用しているかどうかのチェック
        const checkPushCall = (expression: TSESTree.Expression) => {
          if (expression.type !== 'CallExpression') return
          const innerCall = expression.callee
          if (
            innerCall.type === 'MemberExpression' &&
            innerCall.property.type === 'Identifier' &&
            innerCall.property.name === 'push'
          ) {
            // map 内で push を使用している場合に報告
            context.report({
              node: innerCall,
              messageId: 'notUsePushInMap',
            });
          }
        }

        if (body.type === 'BlockStatement') {
          // map(i=> {}) のかたちのもの
          body.body.forEach(statement => {
            if (statement.type === 'ExpressionStatement') {
              checkPushCall(statement.expression)
            }
          });
        } else {
          // map(i=> {}) のかたち以外のもの
          checkPushCall(body)
        }

      }
    }
  }
}

module.exports = notUsePushInMapRule

詳細は省きますが、このように作成しました。

package.json
{
  "name": "eslint-plugin-hitasura-system-develop",
  "version": "0.0.1",
  "description": "",
  "main": "lib/index.js",
  "scripts": {
    "build": "tsc", 
  },
  "files": [
    "lib"
  ],
  "private": false,
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^8.8.1",
    "@typescript-eslint/parser": "^8.8.1",
    "eslint": "^9.12.0",
    "typescript": "^5.6.3"
  }
}

このような感じで、srcフォルダ内をbuildしたときにlibにexportするように設定しました。

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2019",
    "module": "CommonJS",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "outDir": "lib"
  },
  "include": ["src/**/*.ts"]
}

このように実装し、以下コマンドでbuildします。

npm run build

すると、libフォルダが作成され、buildされます。

実際に試してみる

既存のソースコード(今回はNext.jsのソースコード)でインストールして呼び出してみます。

npm install ../eslint-plugin-hitasura-system-develop

こんな感じでローカルのリポジトリをインストールします。

そして、Next.jsの.eslintrc.jsonを以下のように修正します。

.eslintrc.json

{
  "env": {
    "browser": true,
    "es2021": true,
    "jest": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
  ],
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 12,
    "sourceType": "module"
  },
  "plugins": [
    "hitasura-system-develop"  // 追加
  ],
  "rules": {
    "hitasura-system-develop/no-use-push-in-map": "warn"  // 追加
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  },
  "root": true
}

そして、どこかのTSファイルに以下を追加します。

    const a = [1, 2, 3]
    const b = []
    a.map(i => b.push(i))
    a.map(i => { b.push(i) })

この状態で以下コマンドを実行して解析します。

npm run lint

すると以下のようにWarningとして出てきたら成功です!

% npm run lint

> next-example-app@0.1.0 lint
> next lint

./src/views/Buttond/index.tsx
6:16  Warning: Do not use push inside a map method.  hitasura-system-develop/no-use-push-in-map
7:18  Warning: Do not use push inside a map method.  hitasura-system-develop/no-use-push-in-map

おわりに

この記事での質問や、間違っている、もっといい方法があるといったご意見などありましたらご指摘していただけると幸いです。

最後まで読んでいただきありがとうございました!

参考

次の記事

1
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?