refactor!: 💥 ♻️ restructure the config and presets

Now each config is related to a specific language and or purpose,
having different "flavors" or levels of strictness for them

Presets now have the purpose of simply grouping said configs for easier
of them.

BREAKING CHANGE
This commit is contained in:
Guz013
2023-08-30 15:31:13 -03:00
parent 9043156913
commit b28c7d2c62
18 changed files with 138 additions and 430 deletions

View File

@@ -1,6 +1,5 @@
import { configs, defineConfig, presets } from '@eslegant/config'; import { presets } from '@eslegant/config';
export default defineConfig([ export default [
...presets.default, ...presets.recommended,
configs.environments.node, ];
]);

View File

@@ -1,20 +1,6 @@
import type { Config, EnvOptions } from './src/types';
import type { Linter } from 'eslint'; import type { Linter } from 'eslint';
/**
* Helper functions for creating/configuring ESLint.
* @param config - Array or function returning an array of ESLint's configuration objects array to be used.
* @param environment - An object with environment variables to be declared and used by the configuration.
* @returns The array of ESLint's configuration objects.
*/
async function defineConfig(config: Config, environment?: EnvOptions): Promise<Linter.FlatConfig[]>;
const configs: Readonly<{ const configs: Readonly<{
/**
* **This configuration is necessary to be used before any other one**.
* Common configuration for using ESLit rules overrides.
*/
common: Linter.FlatConfig
/** /**
* Recommended configuration overrides of ESLit * Recommended configuration overrides of ESLit
*/ */
@@ -27,31 +13,10 @@ const configs: Readonly<{
* Typescript specific configuration overrides * Typescript specific configuration overrides
*/ */
typescript: Linter.FlatConfig typescript: Linter.FlatConfig
/**
* Configuration objects for different development environments.
*/
environments: {
/**
* Configuration for Node development environment
*/
node: Linter.FlatConfig
/**
* Configuration for Deno development environment
*/
deno: Linter.FlatConfig
/**
* Configuration for browser development environment
*/
browser: Linter.FlatConfig
}
/**
* JSDoc rules overrides
*/
jsdoc: Linter.FlatConfig
}>; }>;
const presets: Readonly<{ const presets: Readonly<{
default: Linter.FlatConfig[] recommended: Linter.FlatConfig[]
}>; }>;
export { presets, configs, defineConfig }; export { configs, presets };

View File

@@ -1,53 +0,0 @@
import tsESLint from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
// eslint-disable-next-line import/namespace, import/default, import/no-named-as-default, import/no-named-as-default-member
import jsdoc from 'eslint-plugin-jsdoc';
import importPlugin from 'eslint-plugin-i';
/**
* **This configuration is necessary to be used before any other one**.
* Common configuration for using ESLit rules overrides.
* @type {Readonly<import('eslint').Linter.FlatConfig>}
*/
const config = {
files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'],
plugins: {
'@typescript-eslint': tsESLint,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'jsdoc': jsdoc,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'import': importPlugin,
},
settings: {
'import/extensions': ['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts'],
'import/parsers': {
'@typescript-eslint/parser': ['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts'],
},
'import/resolver': {
typescript: true,
node: true,
},
},
languageOptions: {
parser: tsParser,
parserOptions: {
project: process.env.ESLIT_TSCONFIG ?? [
'./{ts,js}config{.eslint,}.json',
'./packages/*/{ts,js}config{.eslint,}.json',
'./apps/*/{ts,js}config{.eslint,}.json',
],
tsconfigRootDir: process.env.ESLIT_ROOT ?? process.cwd(),
},
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
ecmaVersion: (
/** @type {import('eslint').Linter.ParserOptions['ecmaVersion']} */
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
() => {return JSON.parse(process.env.ESLIT_ECMASCRIPT ?? '"latest"');}
)(),
},
rules: {
...importPlugin.configs['typescript'].rules,
},
};
export default config;

View File

@@ -0,0 +1,42 @@
import tsESLint from '@typescript-eslint/eslint-plugin';
import { tsFiles, jsFiles } from '../constants.js';
// @ts-expect-error TEMPORALLY COMMENT
import tsParser from '@typescript-eslint/parser';
import jsdocPlugin from 'eslint-plugin-jsdoc';
import importPlugin from 'eslint-plugin-i';
import process from 'node:process';
/** @type {import('eslint').Linter.FlatConfig}*/
const config = {
files: [...tsFiles, ...jsFiles],
plugins: {
'@typescript-eslint': tsESLint,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'jsdoc': jsdocPlugin,
'import': importPlugin,
},
settings: {
'import/extensions': [...tsFiles, ...jsFiles],
'import/parsers': {
'@typescript-eslint/parser': [...tsFiles, ...jsFiles ],
},
'import/resolver': {
typescript: true,
node: true,
},
},
languageOptions: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
parser: tsParser,
parserOptions: {
project: process.env.ESLEGANT_TSCONFIG ?? [
'./{ts,js}config{.eslint,}.json',
'./packages/*/{ts,js}config{.eslint,}.json',
'./apps/*/{ts,js}config{.eslint,}.json',
],
tsconfigRootDir: process.env.ESLEGANT_ROOT ?? process.cwd(),
},
},
};
export default config;

View File

@@ -1,45 +0,0 @@
import globals from 'globals';
/**
* Configuration for Node development environment
* @type {import('eslint').Linter.FlatConfig}
*/
const node = {
files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'],
languageOptions: {
globals: {
...globals.nodeBuiltin,
},
},
};
/**
* Configuration for Deno development environment
* @type {import('eslint').Linter.FlatConfig}
*/
const deno = {
files: ['**/*.js', '**/*.ts'],
languageOptions: {
globals: {
Deno: true,
...globals.browser,
},
},
};
/**
* Configuration for browser development environment
* @type {import('eslint').Linter.FlatConfig}
*/
const browser = {
files: ['**/*.js', '**/*.ts'],
languageOptions: {
globals: {
Deno: true,
...globals.browser,
},
},
};
const environments = { node, deno, browser };
export default environments;

View File

@@ -1,8 +1,8 @@
/** import coreConfig from './core.js';
* Formatting rules/configuration overrides for Javascript and Typescript
* @type {Readonly<import('eslint').Linter.FlatConfig>} /** @type {import('eslint').Linter.FlatConfig} */
*/ const recommended = {
const config = { ...coreConfig,
files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'], files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'],
rules: { rules: {
// Formatting rules // Formatting rules
@@ -24,28 +24,16 @@ const config = {
'@typescript-eslint/comma-dangle': ['error', 'always-multiline'], '@typescript-eslint/comma-dangle': ['error', 'always-multiline'],
'indent': 'off', 'indent': 'off',
'@typescript-eslint/indent': ['error', (() => { '@typescript-eslint/indent': ['error', 'tab', { SwitchCase: 1,
/** @type {import('../types').EnvOptions['ESLIT_INDENT']} */
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const indent = JSON.parse(process.env.ESLINT_INDENT ?? '"tab"');
if (indent === 'space') return 2;
else return indent;
})(), {
SwitchCase: 1,
VariableDeclarator: 1, VariableDeclarator: 1,
outerIIFEBody: 1, outerIIFEBody: 1,
MemberExpression: 1, MemberExpression: 1,
FunctionDeclaration: { parameters: 1, body: 1 }, FunctionDeclaration: { parameters: 1,
FunctionExpression: { parameters: 1, body: 1 }, body: 1 }, FunctionExpression: { parameters: 1,
CallExpression: { arguments: 1 }, body: 1 }, CallExpression: { arguments: 1 }, ArrayExpression: 1,
ArrayExpression: 1,
ObjectExpression: 1, ObjectExpression: 1,
ImportDeclaration: 1, ImportDeclaration: 1,
flatTernaryExpressions: false, flatTernaryExpressions: false, offsetTernaryExpressions: true, ignoreComments: false, ignoredNodes: [
offsetTernaryExpressions: true,
ignoreComments: false,
ignoredNodes: [
'TemplateLiteral *', 'TemplateLiteral *',
'JSXElement', 'JSXElement',
'JSXElement > *', 'JSXElement > *',
@@ -56,7 +44,7 @@ const config = {
'JSXSpreadAttribute', 'JSXSpreadAttribute',
'JSXExpressionContainer', 'JSXExpressionContainer',
'JSXOpeningElement', 'JSXOpeningElement',
'JSXClosingElement', 'Element',
'JSXFragment', 'JSXFragment',
'JSXOpeningFragment', 'JSXOpeningFragment',
'JSXClosingFragment', 'JSXClosingFragment',
@@ -83,13 +71,13 @@ const config = {
'@typescript-eslint/object-curly-spacing': ['error', 'always'], '@typescript-eslint/object-curly-spacing': ['error', 'always'],
'quotes': 'off', 'quotes': 'off',
'@typescript-eslint/quotes': ['error', process.env.ESLINT_QUOTES ?? 'single'], '@typescript-eslint/quotes': ['error', 'single'],
'semi': 'off', 'semi': 'off',
'@typescript-eslint/semi': ['error', 'always'], '@typescript-eslint/semi': ['error', 'always'],
'space-before-blocks': 'off', 'space-before-blocks': 'off',
'@typescript-eslint/space-before-blocks': ['error', process.env.ESLIT_SEMI ?? 'always'], '@typescript-eslint/space-before-blocks': ['error', 'always'],
'space-before-function-paren': 'off', 'space-before-function-paren': 'off',
'@typescript-eslint/space-before-function-paren': ['error', { '@typescript-eslint/space-before-function-paren': ['error', {
@@ -103,4 +91,11 @@ const config = {
}, },
}; };
export default config;
/** @type {import('eslint').Linter.FlatConfig} */
const strict = {
...recommended,
};
const formatting = { recommended, strict };
export default formatting;

View File

@@ -1,9 +1,8 @@
import formatting from './formatting.js';
import jsdoc from './jsdoc.js';
import typescript from './typescript.js';
import recommended from './recommended.js';
import environments from './environments.js';
import common from './common.js';
const configs = { formatting, jsdoc, typescript, recommended, environments, common }; import core from './core.js';
import javascript from './javascript.js';
import typescript from './typescript.js';
import formatting from './formatting.js';
const configs = { core, javascript, typescript, formatting };
export default configs; export default configs;

View File

@@ -0,0 +1,28 @@
import coreConfig from './core.js';
import tsESLint from '@typescript-eslint/eslint-plugin';
import js from '@eslint/js';
import importPlugin from 'eslint-plugin-i';
import jsdocPlugin from 'eslint-plugin-jsdoc';
/** @type {import('eslint').Linter.FlatConfig}*/
const recommended = {
...coreConfig,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
rules: {
...js.configs.recommended.rules,
...tsESLint.configs.recommended.rules,
...tsESLint.configs['recommended-requiring-type-checking'].rules,
...tsESLint.configs['eslint-recommended'].rules,
...tsESLint.configs.strict.rules,
...importPlugin.configs['recommended'].rules,
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
...jsdocPlugin.configs['recommended-typescript-flavor-error'].rules,
},
};
/** @type {import('eslint').Linter.FlatConfig}*/
const strict = {
...recommended,
};
const javascript = { recommended, strict };
export default javascript;

View File

@@ -1,15 +0,0 @@
import jsdoc from 'eslint-plugin-jsdoc';
/**
* JSDoc rules overrides
* @type {Readonly<import('eslint').Linter.FlatConfig>}
*/
const config = {
files: ['**/*.js', '**/*.cjs', '**/*.mjs', '**/*.ts', '**/*.cts', '**/*.mts'],
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
rules: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
...jsdoc.configs['recommended-typescript-flavor-error'].rules,
},
};
export default config;

View File

@@ -1,85 +0,0 @@
import tsESlint from '@typescript-eslint/eslint-plugin';
import js from '@eslint/js';
import importPlugin from 'eslint-plugin-i';
/**
* Recommended configuration overrides of ESLit
* @type {Readonly<import('eslint').Linter.FlatConfig>}
*/
const config = {
rules: {
...js.configs.recommended.rules,
...tsESlint.configs.recommended.rules,
...tsESlint.configs['recommended-requiring-type-checking'].rules,
...tsESlint.configs['eslint-recommended'].rules,
...tsESlint.configs.strict.rules,
...importPlugin.configs['recommended'].rules,
'import/extensions': ['error', 'always', { ignorePackages: true }],
'import/no-anonymous-default-export': ['error'],
'import/no-absolute-path': 'error',
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-cycle': 'error',
'import/no-deprecated': 'error',
'import/no-duplicates': ['error', { 'prefer-inline': false }],
'import/no-empty-named-blocks': 'error',
'import/no-extraneous-dependencies': 'error',
'import/no-import-module-exports': 'error',
'import/no-mutable-exports': 'error',
'import/no-named-as-default-member': 'error',
'import/no-named-as-default': 'warn',
'import/no-named-default': 'error',
'import/no-namespace': 'error',
'import/no-relative-packages': 'error',
'import/no-self-import': 'error',
'import/no-unassigned-import': ['error', { allow: ['**/*.{scss,less,css}'] }],
'import/no-useless-path-segments': 'error',
'import/prefer-default-export': 'error',
'@typescript-eslint/ban-ts-comment': ['error', {
'ts-ignore': 'allow-with-description',
}],
'@typescript-eslint/ban-tslint-comment': 'error',
'@typescript-eslint/no-require-imports': 'error',
// Extension rules
'no-dupe-class-members': 'off',
'@typescript-eslint/no-dupe-class-members': 'error',
'no-invalid-this': 'off',
'@typescript-eslint/no-invalid-this': 'error',
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': 'error',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'error',
'no-empty-function': 'off',
'@typescript-eslint/no-empty-function': 'error',
},
};
export default config;

View File

@@ -1,18 +1,18 @@
import jsdoc from 'eslint-plugin-jsdoc';
/** import coreConfig from './core.js';
* Typescript specific configuration overrides import { tsFiles } from '../constants.js';
* @type {Readonly<import('eslint').Linter.FlatConfig>} import jsdocPlugin from 'eslint-plugin-jsdoc';
*/ import importPlugin from 'eslint-plugin-i';
const config = {
files: ['**/*.ts', '**/*.cts', '**/*.mts'], /** @type {import('eslint').Linter.FlatConfig}*/
// See plugins['jsdoc'] on index.js for more info on this error const recommended = {
...coreConfig,
files: [...tsFiles],
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
rules: { rules: {
// See plugins['jsdoc'] on index.js for more info on this error
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
...jsdoc.configs['recommended-typescript-error'].rules, ...jsdocPlugin.configs['recommended-typescript-error'].rules,
...importPlugin.configs['typescript'].rules,
'@typescript-eslint/adjacent-overload-signatures': 'error', '@typescript-eslint/adjacent-overload-signatures': 'error',
'@typescript-eslint/array-type': 'error', '@typescript-eslint/array-type': 'error',
@@ -30,35 +30,14 @@ const config = {
'@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-for-of': 'error',
'@typescript-eslint/prefer-function-type': 'error', '@typescript-eslint/prefer-function-type': 'error',
'@typescript-eslint/prefer-namespace-keyword': 'error', '@typescript-eslint/prefer-namespace-keyword': 'error',
'@typescript-eslint/explicit-function-return-type': 'error',
...( '@typescript-eslint/no-inferrable-types': 'error',
/** @type {() => import('eslint').Linter.RulesRecord} */
() => {
/** @type {import('../types').inferrableTypesOptions} */
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const inferrableTypes = JSON.parse(process.env.ESLIT_INFER_TYPES ?? '"never"');
if (typeof inferrableTypes === 'string') {
return {
'@typescript-eslint/explicit-function-return-type': inferrableTypes === 'always' ? 'off' : 'error',
'@typescript-eslint/no-inferrable-types': inferrableTypes === 'always' ? 'off' : 'error',
};
}
else {
return {
'@typescript-eslint/explicit-function-return-type': inferrableTypes[1].returnValues ? 'off' : 'error',
'@typescript-eslint/no-inferrable-types': [
inferrableTypes[0] === 'always' ? 'off' : 'error',
{
ignoreParameters: inferrableTypes[1].parameters ?? false,
ignoreProperties: inferrableTypes[1].properties ?? false,
},
],
};
}
}
)(),
}, },
}; };
export default config;
/** @type {import('eslint').Linter.FlatConfig}*/
const strict = {
...recommended,
};
const typescript = { recommended, strict };
export default typescript;

View File

@@ -0,0 +1,4 @@
const jsFiles = ['**/*.js', '**/*.mjs', '**/*.cjs', '**/*.jsx'];
const tsFiles = ['**/*.ts', '**/*.mts', '**/*.cts', '**/*.tsx'];
export { jsFiles, tsFiles };

View File

@@ -1,17 +0,0 @@
import { FlatCompat } from '@eslint/eslintrc';
import javascript from '@eslint/js';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
// mimic CommonJS variables
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const eslintrc = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: javascript.configs.recommended,
allConfig: javascript.configs.all,
});
export { eslintrc, __filename, __dirname };

View File

@@ -1,20 +1,3 @@
import { eslintrc } from './eslintrc-compact.js';
/**
* @param {import('./types').Config} config
* Array or function returning an array of ESLint's configuration objects array to be used.
* @param {import('./types').EnvOptions | undefined} environment
* An object with environment variables to be declared and used by the configuration.
* @returns {Promise<import('eslint').Linter.FlatConfig[]>}
* The array of ESLint's configuration objects.
*/
export async function defineConfig(config, environment = {}) {
for (const [key, value] of Object.entries(environment)) {
process.env[key] = JSON.stringify(value);
}
return typeof config === 'function' ? await config(eslintrc) : config;
}
export { default as configs } from './configs/index.js'; export { default as configs } from './configs/index.js';
export { default as presets } from './presets/index.js'; export { default as presets } from './presets/index.js';

View File

@@ -1,23 +0,0 @@
import configs from '../configs/index.js';
/**
* @type {Readonly<import('eslint').Linter.FlatConfig[]>}
*/
const preset = [
{
ignores: [
'**/node_modules',
'**/dist',
'**/fixtures',
'**/pnpm-lock.yaml',
'**/yarn.lock',
'**/package-lock.json',
],
},
configs.common,
configs.recommended,
configs.formatting,
configs.jsdoc,
configs.typescript,
];
export default preset;

View File

@@ -1,5 +1,4 @@
import recommended from './recommended.js';
import defaultPreset from './default.js'; const presets = { recommended };
const presets = { default: defaultPreset };
export default presets; export default presets;

View File

@@ -0,0 +1,9 @@
import configs from '../configs/index.js';
/** @type {import('eslint').Linter.FlatConfig[]}*/
const recommended = [
configs.javascript.recommended,
configs.typescript.recommended,
configs.formatting.recommended,
];
export default recommended;

View File

@@ -1,56 +0,0 @@
import type { FlatCompat } from '@eslint/eslintrc';
import type { Linter } from 'eslint';
type MaybePromise<T> = Promise<T> | T;
type Config = Linter.FlatConfig[] | ((eslintrc: FlatCompat) => MaybePromise<Linter.FlatConfig[]>);
interface EnvOptions {
ESLIT_TSCONFIG?: string | string[] | true
ESLIT_ROOT?: string
ESLIT_INDENT?: 'tab' | 'space' | number
ESLIT_ECMASCRIPT?: Linter.ParserOptions['ecmaVersion']
ESLIT_QUOTES?: 'single' | 'double'
ESLIT_SEMI?: 'never' | 'always'
/**
* Typescript's type-checking is able to infer types from parameters.
* So using an explicit `:` type annotation isn't obligatory.
*
* But, **by default in strict mode**, type annotations are always mandated to make
* the code more readable, explicit and robust to changes.
*
* See {@link https://typescript-eslint.io/rules/no-inferrable-types typescript-eslint documentation }
* for more info.
* ---
* **Option: `never`** (default)
* Types are always explicit in Typescript
* @example ```ts
// Typescript
const id: number = 10;
const name: string = 'foo';
```
* ---
* **Option: `always`**
* Types are always inferred in Typescript
* @example ```ts
// Typescript
const id = 10;
const name = 'foo';
```
*/
ESLIT_INFER_TYPES?: inferrableTypesOptions
[ENV: string]: unknown
}
type inferrableTypesOptions = [
'never' | 'always',
{
/** @see {@link https://typescript-eslint.io/rules/no-inferrable-types#ignoreparameters} */
parameters?: boolean
/** @see {@link https://typescript-eslint.io/rules/no-inferrable-types#ignoreproperties} */
properties?: boolean
/** @see {@link https://typescript-eslint.io/rules/explicit-function-return-type} */
returnValues?: boolean
},
] | 'never' | 'always';
export type { inferrableTypesOptions, EnvOptions, Config };