Skip to content

Commit

Permalink
Merge pull request #154 from hatena/fine-grained-configs
Browse files Browse the repository at this point in the history
React / Next.js 向けの設定を個別に利用可能にする
  • Loading branch information
susisu authored Dec 26, 2024
2 parents 0b9015d + 17bd4cc commit 86dd14e
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 54 deletions.
63 changes: 18 additions & 45 deletions lib/config/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
'use strict';

const jsPlugin = require('@eslint/js');
const nextPlugin = require('@next/eslint-plugin-next');
const prettierConfig = require('eslint-config-prettier');
const importPlugin = require('eslint-plugin-import');
const jsxA11yPlugin = require('eslint-plugin-jsx-a11y');
const reactPlugin = require('eslint-plugin-react');
const reactHooksPlugin = require('eslint-plugin-react-hooks');
const tsEslint = require('typescript-eslint');
const rules = require('../rules/index.js');
const nextConfig = require('./next.js');
const reactConfig = require('./react.js');

/**
* @typedef ConfigOptions
Expand Down Expand Up @@ -37,7 +35,7 @@ const rules = require('../rules/index.js');
* ESLint の設定を作る
* @param {ConfigOptions} [options] オプション
* @param {readonly import('typescript-eslint').ConfigWithExtends[]} [configs] カスタム設定の配列
* @returns {import('typescript-eslint').Config} 設定の配列
* @returns {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigArray} 設定の配列
*/
function config(options, configs) {
const tsProject = options?.tsProject ?? true;
Expand All @@ -52,17 +50,10 @@ function config(options, configs) {
{
name: '@hatena/eslint-config-hatena/global-settings',
plugins: {
'@typescript-eslint': tsEslint.plugin,
'import': importPlugin,
'react': reactPlugin,
'react-hooks': reactHooksPlugin,
'@next/next': nextPlugin,
'jsx-a11y': jsxA11yPlugin,
'@typescript-eslint': tsEslint.plugin,
},
settings: {
'react': {
version: 'detect',
},
// 参考: https://github.com/import-js/eslint-plugin-import/blob/main/config/typescript.js
'import/extensions': ['.js', '.jsx', '.cjs', '.mjs', '.ts', '.tsx', '.cts', '.mts'],
'import/external-module-folders': ['node_modules', 'node_modules/@types'],
Expand All @@ -85,7 +76,6 @@ function config(options, configs) {
files: ['**/*.{js,jsx,cjs,mjs}', '**/*.{ts,tsx,cts,mts}'],
languageOptions: {
ecmaVersion: 'latest',
parserOptions: react ? { ecmaFeatures: { jsx: true } } : {},
},
},
{
Expand Down Expand Up @@ -116,44 +106,22 @@ function config(options, configs) {
},
},
},
{
name: '@hatena/eslint-config-hatena/language-settings/jsx',
files: ['**/*.jsx', '**/*.tsx'],
languageOptions: {
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
},
// # ルール設定
{
name: '@hatena/eslint-config-hatena/rules/all',
files: ['**/*.{js,jsx,cjs,mjs}', '**/*.{ts,tsx,cts,mts}'],
extends: [{ rules: jsPlugin.configs.recommended.rules }, { rules: importPlugin.configs.recommended.rules }],
rules: rules.javascript,
},
...(react || next
? [
{
name: '@hatena/eslint-config-hatena/rules/react',
files: ['**/*.{js,jsx,cjs,mjs}', '**/*.{ts,tsx,cts,mts}'],
extends: [
{ rules: reactPlugin.configs.recommended.rules },
{ rules: reactPlugin.configs['jsx-runtime'].rules },
{ rules: reactHooksPlugin.configs.recommended.rules },
],
rules: rules.react,
},
]
: []),
...(next
? [
{
name: '@hatena/eslint-config-hatena/rules/next',
files: ['**/*.{js,jsx,cjs,mjs}', '**/*.{ts,tsx,cts,mts}'],
extends: [
{
rules: {
...nextPlugin.configs.recommended.rules,
...(next === 'strict' ? nextPlugin.configs['core-web-vitals'].rules : {}),
},
},
],
rules: rules.next,
},
]
: []),
{
name: '@hatena/eslint-config-hatena/rules/ts',
files: ['**/*.{ts,tsx,cts,mts}'],
Expand All @@ -164,6 +132,8 @@ function config(options, configs) {
],
rules: rules.typescript,
},
// # ライブラリ・フレームワーク向けの設定
...(next ? nextConfig({ strict: next === 'strict' }) : react ? reactConfig() : []),
// # カスタム設定
...(configs ?? []),
// # フォーマットに関するルールを無効化
Expand All @@ -178,4 +148,7 @@ function config(options, configs) {
);
}

config.react = reactConfig;
config.next = nextConfig;

module.exports = config;
57 changes: 57 additions & 0 deletions lib/config/next.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

const nextPlugin = require('@next/eslint-plugin-next');
const jsxA11yPlugin = require('eslint-plugin-jsx-a11y');
const tsEslint = require('typescript-eslint');
const rules = require('../rules/index.js');
const reactConfig = require('./react.js');

/**
* @typedef ConfigOptions
* @property {boolean | undefined} [strict]
* true の場合, Core Web Vitals に関するルールを追加で有効にする.
* デフォルト: false
*/

/**
* Next.js を使用するプロジェクト向けの設定. React 向けの設定も内包している.
* @param {ConfigOptions} [options] オプション
* @returns {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigArray} 設定の配列
*/
function config(options) {
const strict = options?.strict ?? false;

// NOTE: files は extends によって一括で上書きされることもある
const defaultFiles = ['**/*.{js,jsx,cjs,mjs}', '**/*.{ts,tsx,cts,mts}'];

return tsEslint.config(
{
name: '@hatena/eslint-config-hatena/next/react',
files: defaultFiles,
extends: reactConfig(),
},
{
name: '@hatena/eslint-config-hatena/next/plugins',
files: defaultFiles,
plugins: {
'@next/next': nextPlugin,
'jsx-a11y': jsxA11yPlugin,
},
},
{
name: '@hatena/eslint-config-hatena/next/rules',
files: defaultFiles,
extends: [
{
rules: {
...nextPlugin.configs.recommended.rules,
...(strict ? nextPlugin.configs['core-web-vitals'].rules : {}),
},
},
],
rules: rules.next,
},
);
}

module.exports = config;
43 changes: 43 additions & 0 deletions lib/config/react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';

const reactPlugin = require('eslint-plugin-react');
const reactHooksPlugin = require('eslint-plugin-react-hooks');
const tsEslint = require('typescript-eslint');
const rules = require('../rules/index.js');

/**
* React を使用するプロジェクト向けの設定
* @returns {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigArray} 設定の配列
*/
function config() {
// NOTE: files は extends によって一括で上書きされることもある
const defaultFiles = ['**/*.{js,jsx,cjs,mjs}', '**/*.{ts,tsx,cts,mts}'];

return tsEslint.config(
{
name: '@hatena/eslint-config-hatena/react/plugins',
files: defaultFiles,
plugins: {
'react': reactPlugin,
'react-hooks': reactHooksPlugin,
},
settings: {
react: {
version: 'detect',
},
},
},
{
name: '@hatena/eslint-config-hatena/react/rules',
files: defaultFiles,
extends: [
{ rules: reactPlugin.configs.recommended.rules },
{ rules: reactPlugin.configs['jsx-runtime'].rules },
{ rules: reactHooksPlugin.configs.recommended.rules },
],
rules: rules.react,
},
);
}

module.exports = config;
41 changes: 32 additions & 9 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ParserOptions } from '@typescript-eslint/parser';
import { Config, ConfigWithExtends } from 'typescript-eslint';
import { TSESLint } from '@typescript-eslint/utils';
import { ConfigWithExtends } from 'typescript-eslint';

type ConfigOptions = Readonly<
Partial<{
Expand Down Expand Up @@ -37,13 +38,35 @@ type ConfigOptions = Readonly<
}>
>;

/**
* ESLint の設定を作る
* @param options オプション
* @param configs カスタム設定の配列
* @returns 設定の配列
*/
declare function config(options?: ConfigOptions, configs?: readonly ConfigWithExtends[]): Config[];
type NextConfigOptions = Readonly<
Partial<{
/**
* true の場合, Core Web Vitals に関するルールを追加で有効にする.
* デフォルト: false
*/
strict: boolean | undefined;
}>
>;

declare const config: {
/**
* ESLint の設定を作る
* @param options オプション
* @param configs カスタム設定の配列
* @returns 設定の配列
*/
(options?: ConfigOptions, configs?: readonly ConfigWithExtends[]): TSESLint.FlatConfig.ConfigArray;
/**
* React を使用するプロジェクト向けの設定
* @returns 設定の配列
*/
react: () => TSESLint.FlatConfig.ConfigArray;
/**
* Next.js を使用するプロジェクト向けの設定. React 向けの設定も内包している.
* @returns 設定の配列
*/
next: (options?: NextConfigOptions) => TSESLint.FlatConfig.ConfigArray;
};

export { type ConfigOptions, config };
export { type ConfigOptions, type NextConfigOptions, config };
export default config;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"@types/eslint": "^9.6.1",
"@typescript-eslint/eslint-plugin": "^8.17.0",
"@typescript-eslint/parser": "^8.17.0",
"@typescript-eslint/utils": "^8.17.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-node": "^0.3.9",
"eslint-import-resolver-typescript": "^3.7.0",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 86dd14e

Please sign in to comment.