はじめに
こんにちは、エンジニアのkeitaMaxです。
前回ESLintプラグインを作成しました。
今回は前回作成したものを修正して解析するファイルを選択できるようにしたいと思います。
やりたいこと
"hitasura-system-develop/no-use-push-in-map": ["warn",{
"include":["./src/**/*.ts","./src/**/*.tsx"],
"exclude":["./src/test/**/*.ts"]
}]
このような感じで解析したファイルと解析したくないファイルをプラグインを使用するESLintの設定ファイル内で書けるようにします。
実装
以下のよう実装しました。
notUsePushInMap.ts
import { TSESTree } from '@typescript-eslint/utils'
import { RuleModule } from '@typescript-eslint/utils/dist/eslint-utils'
// 追加
type Options = {
include?: string[],
exclude?: string[]
}
// [Options] 追加
export const notUsePushInMapRule: RuleModule<'notUsePushInMap', [Options]> = {
meta: {
type: 'suggestion',
docs: {
description: 'Disallow the use of push inside map method',
},
messages: {
notUsePushInMap: "Do not use push inside a map method."
},
// schema 追加
schema: [
{
type: "object",
additionalProperties: false,
properties: {
include: {
type: "array",
items: {
type: "string"
},
minItems: 0
},
exclude: {
type: "array",
items: {
type: "string"
},
minItems: 0
}
}
}
]
},
// defaultOptions 追加
defaultOptions: [{
include: ["./src/**/*.js", "./src/**/*.ts", "./src/**/*.jsx", "./src/**/*.tsx"],
exclude: [],
}],
create(context) {
// 処理 追加
const options = context.options[0] || {}
const filename = context.filename
const includePatterns = options.include?.map((pattern: string) => new RegExp(pattern.replace(/\*\*/g, '.*')))
const excludePatterns = options.exclude?.map((pattern: string) => new RegExp(pattern.replace(/\*\*/g, '.*')))
// ファイルが対象外かどうかを判定
const isFileExcluded = () => {
const isIncluded = includePatterns?.some((regex) => regex.test(filename)) ?? true
const isExcluded = excludePatterns?.some((regex) => regex.test(filename)) ?? false
return !isIncluded || isExcluded;
};
// ファイルが対象外であればルールをスキップ
if (isFileExcluded()) {
return {}
}
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
type Options = {
include?: string[],
exclude?: string[]
}
export const notUsePushInMapRule: RuleModule<'notUsePushInMap', [Options]> = {
ここで入力値にOptionsを追加しました。
schema: [
{
type: "object",
additionalProperties: false,
properties: {
include: {
type: "array",
items: {
type: "string"
},
minItems: 0
},
exclude: {
type: "array",
items: {
type: "string"
},
minItems: 0
}
}
}
]
ここで入力値をcreate内で使用したいので方を書いています。
defaultOptions: [{
include: ["./src/**/*.js", "./src/**/*.ts", "./src/**/*.jsx", "./src/**/*.tsx"],
exclude: [],
}],
入力されたデフォルトの値を宣言しています。
const options = context.options[0] || {}
const filename = context.filename
const includePatterns = options.include?.map((pattern: string) => new RegExp(pattern.replace(/\*\*/g, '.*')))
const excludePatterns = options.exclude?.map((pattern: string) => new RegExp(pattern.replace(/\*\*/g, '.*')))
// ファイルが対象外かどうかを判定
const isFileExcluded = () => {
const isIncluded = includePatterns?.some((regex) => regex.test(filename)) ?? true
const isExcluded = excludePatterns?.some((regex) => regex.test(filename)) ?? false
return !isIncluded || isExcluded;
};
// ファイルが対象外であればルールをスキップ
if (isFileExcluded()) {
return {}
}
これで実際に解析しているファイルが対象のものなのかを判定しています。
実行
以下コマンドでビルドし、解析したいソースコードでインストールしてlintを流してみます。
npm run build
npm install ../eslint-plugin-hitasura-system-develop
以下のように解析したいソースの.eslintrc.json
を修正します。
.eslintrc.json
"hitasura-system-develop/no-use-push-in-map": ["warn",{
"include":["./src/**/*.ts","./src/**/*.tsx"],
"exclude":["./src/test/**/*.ts"]
}]
それで以下のコマンドを実行すればincludeに書いてあるファイルのみを解析してくれて、excludeにあるファイルは解析から外してくれます。
npm run lint
おわりに
この記事での質問や、間違っている、もっといい方法があるといったご意見などありましたらご指摘していただけると幸いです。
最後まで読んでいただきありがとうございました!