Skip to content

Instantly share code, notes, and snippets.

@tomdavidson
Last active October 24, 2022 17:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomdavidson/e7ac825e66201259713c1b47eb997ed3 to your computer and use it in GitHub Desktop.
Save tomdavidson/e7ac825e66201259713c1b47eb997ed3 to your computer and use it in GitHub Desktop.
// @ts-nocheck
import { env } from 'node:process';
import comments from 'eslint-plugin-eslint-comments';
import imprt from 'eslint-plugin-import'; // 'import' is ambiguous and prettier had trouble
import jest from 'eslint-plugin-jest';
import jestFmt from 'eslint-plugin-jest-formatting';
import n from 'eslint-plugin-n';
import nextjs from '@next/eslint-plugin-next';
import prettierConfig from 'eslint-config-prettier';
import promise from 'eslint-plugin-promise';
import react from 'eslint-plugin-react';
import jsxa11y from 'eslint-plugin-jsx-a11y';
import reactHooks from 'eslint-plugin-react-hooks';
import regexp from 'eslint-plugin-regexp';
import sec from 'eslint-plugin-security';
import sonarjs from 'eslint-plugin-sonarjs';
import tsParser from '@typescript-eslint/parser';
import ts from '@typescript-eslint/eslint-plugin';
import unicorn from 'eslint-plugin-unicorn';
import md from 'eslint-plugin-markdown';
import testLib from 'eslint-plugin-testing-library';
import sb from 'eslint-plugin-storybook';
import func from 'eslint-plugin-functional';
// const env = process.env;
// const comments = require('eslint-plugin-eslint-comments');
// const imprt = require('eslint-plugin-import');
// const jest = require('eslint-plugin-jest');
// const jestFmt = require('eslint-plugin-jest-formatting');
// const n = require('eslint-plugin-n');
// const nextjs = require('@next/eslint-plugin-next');
// const prettierConfig = require('eslint-config-prettier');
// const promise = require('eslint-plugin-promise');
// const react = require('eslint-plugin-react');
// const jsxa11y = require('eslint-plugin-jsx-a11y');
// const reactHooks = require('eslint-plugin-react-hooks');
// const regexp = require('eslint-plugin-regexp');
// const sec = require('eslint-plugin-security');
// const sonarjs = require('eslint-plugin-sonarjs');
// const tsParser = require('@typescript-eslint/parser');
// const ts = require('@typescript-eslint/eslint-plugin');
// const unicorn = require('eslint-plugin-unicorn');
// const md = require('eslint-plugin-markdown');
// const testLib = require('eslint-plugin-testing-library');
// const sb = require('eslint-plugin-storybook');
// const func = require('eslint-plugin-functional');
const IS_HEAVY = Boolean(env.LINT_HEAVY) || Boolean(env.CI);
const JS_FILES = ['**/*?.(md/){js,jsx,cjs,mjs}'];
const TS_FILES = ['**/*?.(md/){ts,tsx}'];
const JS_TS_FILES = ['**/*.?(md/){js,ts,jsx,tsx,cjs,mjs}'];
const REACTJS_FILES = ['**/*.?(md/){t,j}sx'];
// base, starting point
const baseConfig = [
// Base config for MD
{
files: ['**/*.md', '**/*.md/*'],
plugins: {
markdown: md,
},
processor: 'markdown/markdown',
},
// base config for codes
{
languageOptions: {
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
es2022: true,
},
files: JS_TS_FILES,
plugins: {
func,
imprt,
promise,
regexp,
unicorn,
},
rules: {
// disable function overloading entirely rather than defer to @typescript-eslint
'no-redeclare': 2,
'no-nested-ternary': 'off',
...unicorn.configs.recommended.rules,
'unicorn/prefer-at': 1,
'unicorn/better-regex': 2,
'unicorn/consistent-function-scoping': 2,
'unicorn/explicit-length-check': 2,
'unicorn/no-array-push-push': 2,
'unicorn/no-array-reduce': 2,
'unicorn/no-await-expression-member': 2,
'unicorn/no-for-loop': 2,
'unicorn/no-instanceof-array': 2,
'unicorn/no-new-array': 2,
'unicorn/no-new-buffer': 2,
'unicorn/no-unsafe-regex': 2,
'unicorn/no-useless-length-check': 2,
'unicorn/no-useless-spread': 2,
'unicorn/no-useless-undefined': 2,
'unicorn/prefer-array-find': 2,
'unicorn/prefer-array-flat-map': 2,
'unicorn/prefer-array-flat': 2,
'unicorn/prefer-array-index-of': 2,
'unicorn/prefer-array-some': 2,
'unicorn/prefer-date-now': 2,
'unicorn/prefer-default-parameters': 2,
'unicorn/prefer-event-target': 2,
'unicorn/prefer-export-from': [2, { ignoreUsedVariables: true }],
'unicorn/prefer-includes': 2,
'unicorn/prefer-logical-operator-over-ternary': 2,
'unicorn/prefer-native-coercion-functions': 2,
'unicorn/prefer-object-from-entries': 2,
'unicorn/prefer-prototype-methods': 2,
'unicorn/prefer-query-selector': 2,
'unicorn/prefer-spread': 2,
'unicorn/prefer-string-replace-all': 2,
'unicorn/prefer-top-level-await': 2,
'unicorn/prefer-type-error': 2,
'unicorn/throw-new-error': 2,
// promise rules
...promise.configs.recommended.rules,
// "promise/always-return": 2,
// "promise/avoid-new": 1,
// "promise/catch-or-return": 2,
// "promise/no-callback-in-promise": 1,
// "promise/no-native": 0,
// "promise/no-nesting": 1,
// "promise/no-new-statics": 2,
// "promise/no-promise-in-callback": 1,
// "promise/no-return-in-finally": 1,
// "promise/no-return-wrap": 2,
// "promise/param-names": 2,
// "promise/valid-params": 1,
// fp rules - strategic FP.
...func.configs['external-recommended'].rules,
...func.configs.lite.rules,
'func/functional-parameters': 1,
'func/no-conditional-statement': 1,
'func/no-try-statement': 2,
// Too much syntax noise, use an immutable lib if needed.
'func/prefer-readonly-type': 1,
'func/immutable-data': 1,
// import rules for all js/ts
...imprt.configs.recommended.rules,
'imprt/export': 2, // TypeScript compilation already ensures
'imprt/named': 2, // TypeScript compilation already ensures that named imports exist in the referenced module
'imprt/namespace': 2, // TypeScript compilation already ensures
'imprt/default': 2, // TypeScript compilation already ensures
'imprt/no-named-as-default-member': 1,
'imprt/no-unresolved': [2, { commonjs: true }], // required by eslint-import-resolver-typescript.
'imprt/no-unused-modules': [
2,
{
missingExports: true,
unusedExports: true,
ignoreExports: '**/*(rc,.config).{cjs,mjs,js,ts,jsx,tsx}',
},
],
'imprt/no-useless-path-segments': [2, { noUselessIndex: true }],
'imprt/max-dependencies': [
1,
{
max: 8,
ignoreTypeImports: true,
},
],
},
},
// base config and rules only for TS files
{
files: TS_FILES,
languageOptions: {
parser: tsParser,
parserOptions: {
ecmaFeatures: { modules: true },
project: './tsconfig.json',
},
},
plugins: {
func,
imprt,
ts,
},
settings: {
'imprt/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'imprt/resolver': {
typescript: {
alwaysTryTypes: true,
},
node: {
extensions: ['.ts', '.tsx'],
},
},
'imprt/external-module-folders': ['node_modules', 'node_modules/@types'],
'imprt/extensions': ['.ts', '.tsx'],
},
rules: {
...ts.configs['eslint-recommended'].rules,
...ts.configs['recommended'].rules,
'no-undef': 0, // typescript already takes care of this
'no-dupe-class-members': 0, // typescript already takes care of this
'no-return-await': 0, // use @typescript/eslint
'no-throw-literal': 0, // use @typescript/eslint
'no-use-before-define': 0, // use @typescript/eslint
'no-unused-expressions': 0, // use @typescript/eslint
'no-empty-function': 0, // use @typescript/eslint
'require-await': 0, // use @typescript/eslint
'no-unused-vars': 0, // use @typescript/eslint
'dot-notation': 0, // use @typescript/eslint
'no-shadow': 0, // use @typescript/eslint
'ts/return-await': 2,
'ts/no-throw-literal': 2,
'ts/no-use-before-define': 2,
'ts/consistent-type-assertions': 2,
'ts/consistent-type-imports': 2,
'ts/explicit-module-boundary-types': 2,
'ts/no-invalid-void-type': 2,
'ts/no-unnecessary-condition': 2,
'ts/no-unused-expressions': [
2,
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
enforceForJSX: true,
},
],
'ts/no-array-constructor': 0,
'ts/array-type': 2,
'ts/no-empty-function': 2,
'ts/prefer-optional-chain': 2,
'ts/dot-notation': 2,
'ts/no-unsafe-assignment': 0,
'ts/no-shadow': [
2,
{
hoist: 'all',
allow: ['resolve', 'reject', 'done', 'next', 'err', 'error'],
ignoreTypeValueShadow: true,
ignoreFunctionTypeParameterNameValueShadow: true,
},
],
// Too much syntax noise, use an immutable lib if needed.
'ts/prefer-readonly-parameter-types': 0,
// Enable the FP rules that require TS.
'func/no-expression-statement': [2, { ignoreVoid: true }],
'func/no-return-void': 2,
'func/no-method-signature': 2,
'func/no-mixed-type': 2,
'func/prefer-tacit': 2,
// Import rules for import
...imprt.configs.typescript.rules,
'imprt/export': 0, // TypeScript compilation already ensures
'imprt/named': 0, // TypeScript compilation already ensures that named imports exist in the referenced module
'imprt/namespace': 0, // TypeScript compilation already ensures
'imprt/default': 0, // TypeScript compilation already ensures
'imprt/no-named-as-default-member': 1,
'imprt/no-unresolved': [2, { commonjs: true }], // required by eslint-import-resolver-typescript.
},
},
];
// style, fmt, and controls - heavy weight, apply last
const summitConfig = [
{
linterOptions: {
noInlineConfig: false,
},
plugins: {
comments,
func,
imprt,
},
rules: {
// Require a short note when allowing inline config such as disabling a rule.
'comments/require-description': 2,
'comments/disable-enable-pair': 2,
'comments/no-aggregating-enable': 2,
'comments/no-duplicate-disable': 2,
'comments/no-unlimited-disable': 2,
'comments/no-unused-enable': 2,
// Function hoisting for readability
'no-use-before-define': [2, { functions: false, classes: true, variables: true }],
...func.configs.stylistic.rules,
// Import styling
'imprt/first': 1,
'imprt/exports-last': 0, // only works on es6 exports
'imprt/no-duplicates': 2,
'imprt/no-namespace': 1,
'imprt/extensions': [1, 'never'],
'imprt/order': [
2,
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
alphabetize: { order: 'asc' },
},
],
'imprt/newline-after-import': [1, { considerComments: true }],
'imprt/prefer-default-export': 0,
'imprt/no-default-export': 1,
'imprt/no-anonymous-default-export': 2,
'imprt/no-unassigned-import': 2,
'imprt/no-named-as-default': 1,
'imprt/no-named-export': 0,
'imprt/consistent-type-specifier-style': 1,
},
},
// TS & ES6 Modules overrides
{
files: ['**/*{ts,tsx,mjs}'],
plugins: {
imprt,
},
rules: {
'imprt/exports-last': 1,
},
},
// config file overrides
{
files: ['**/*{.config,rc}.{cjs,mjs,js,ts,jsx,tsx}'],
plugins: {
func,
imprt,
},
rules: {
'imprt/no-default-export': 0,
'func/no-expression-statement': 0,
'func/immutable-data': 0,
},
},
prettierConfig,
];
const heavyConfig = [
{
files: JS_TS_FILES,
plugins: {
sonarjs, // works best with TS but does have to have it.
},
rules: {
...sonarjs.configs.recommended.rules,
'sonarjs/cognitive-complexity': 0,
'sonarjs/prefer-immediate-return': 0,
},
},
{
files: ['**/*{ts,tsx}'],
plugins: {
ts,
},
rules: {
// this one is heavy, put it behind a condition
...ts.configs['recommended-requiring-type-checking'].rules,
},
},
];
// Jest configuration
const testConfig = [
{
files: ['**/*.[spec,test].[tj]s?(x)'],
languageOptions: {
jest: true,
node: true,
browser: true,
},
plugins: { testLib, jest, jestFmt },
rules: {
...testLib.configs.react.rules,
...jest.configs.recommended.rules,
...jest.configs.style.rules,
...jestFmt.configs.strict.rules,
},
},
{
// or whatever matches stories specified in .storybook/main.js
files: ['*.stories.@(ts|tsx|js|jsx|mjs|cjs)'],
plugins: { sb },
rules: {
...sb.configs.recommended.rules,
...sb.configs.csf.rules,
...sb.configs['csf-strict'].rules,
},
},
];
// NodeJS additions
const nodejsConfig = [
{
// some of this might not apply to jsx/tsx
files: JS_TS_FILES,
languageOptions: {
node: true,
},
plugins: {
n,
sec,
imprt,
},
rules: {
...n.configs.recommended.rules,
...sec.configs.recommended.rules,
'n/no-unsupported-features/es-syntax': [2, { ignores: ['modules'] }],
'n/no-unsupported-features/node-builtins': 0,
'n/no-missing-import': 2,
'no-path-concat': 2,
'no-process-exit': 2,
'no-sync': 1,
'imprt/no-nodejs-modules': 0,
},
},
{
files: ['**/*.ts'],
plugins: {
n,
},
rules: {
'n/no-missing-import': 0, // Typescript has this covered.
},
},
];
const reactjsConfig = [
{
files: REACTJS_FILES,
plugins: {
react,
},
languageOptions: {
node: true,
browser: 'true',
parser: tsParser,
parserOptions: {
ecmaFeatures: { modules: true, jsx: true },
project: './tsconfig.json',
jsxPragma: null,
},
},
settings: {
react: {
version: 'detect',
},
},
rules: {
...react.configs.recommended.rules,
// https://github.com/jsx-eslint/eslint-plugin-react/blob/8cf47a8ac2242ee00ea36eac4b6ae51956ba4411/index.js#L165-L179
...react.configs['jsx-runtime'].rules,
'react/prop-types': 0,
'react/no-unstable-nested-components': [2, { allowAsProps: false }],
'react/jsx-no-useless-fragment': [2, { allowExpressions: true }],
'react/jsx-filename-extension': [2, { extensions: ['.jsx', '.tsx'] }],
'react/jsx-boolean-value': 2,
'react/jsx-fragments': 2,
'react/destructuring-assignment': 2,
'react/no-multi-comp': 2,
'react/no-array-index-key': 2,
'react/jsx-props-no-spreading': 0,
'react/jsx-sort-props': [
2,
{
callbacksLast: true,
shorthandFirst: true,
shorthandLast: false,
ignoreCase: true,
noSortAlphabetically: true,
multiline: 'last',
reservedFirst: true,
},
],
},
},
{
files: REACTJS_FILES,
plugins: {
jsxa11y,
reactHooks,
},
rules: [
//...jsxa11y.configs.recommended.rules,
// ...reactHooks.configs.recommended.rules,
],
},
];
const nextjsConfig = [
{
files: ['apps/**/*{js,ts,jsx,tsx}'],
settings: {
next: {
rootDir: 'apps/*/',
},
},
plugins: {
'@next/next': nextjs,
},
rules: {
...nextjs.configs.recommended.rules,
...nextjs.configs['core-web-vitals'].rules,
'@next/next/no-html-link-for-pages': 0,
'react/jsx-key': 0,
},
},
];
export default [
// module.exports = [
...baseConfig,
...nodejsConfig,
...nextjsConfig,
...reactjsConfig,
...testConfig,
...(IS_HEAVY ? heavyConfig : []),
...summitConfig,
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment