Skip to content

Instantly share code, notes, and snippets.

@tomdavidson
Last active October 24, 2022 17:20
Show Gist options
  • Select an option

  • Save tomdavidson/e7ac825e66201259713c1b47eb997ed3 to your computer and use it in GitHub Desktop.

Select an option

Save tomdavidson/e7ac825e66201259713c1b47eb997ed3 to your computer and use it in GitHub Desktop.

Revisions

  1. tomdavidson revised this gist Oct 24, 2022. 1 changed file with 0 additions and 5 deletions.
    5 changes: 0 additions & 5 deletions eslint.config.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,4 @@
    // @ts-nocheck
    // import ks from 'eslint-config-skipa/ks';
    // export default ks;

    import { env } from 'node:process';
    import comments from 'eslint-plugin-eslint-comments';
    @@ -356,8 +354,6 @@ const summitConfig = [
    prettierConfig,
    ];

    // heavy, selective application such as only in CI or on prepush hook

    const heavyConfig = [
    {
    files: JS_TS_FILES,
    @@ -460,7 +456,6 @@ const reactjsConfig = [
    parserOptions: {
    ecmaFeatures: { modules: true, jsx: true },
    project: './tsconfig.json',
    // https://github.com/jsx-eslint/eslint-plugin-react/blob/8cf47a8ac2242ee00ea36eac4b6ae51956ba4411/index.js#L165-L179
    jsxPragma: null,
    },
    },
  2. tomdavidson created this gist Oct 24, 2022.
    543 changes: 543 additions & 0 deletions eslint.config.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,543 @@
    // @ts-nocheck
    // import ks from 'eslint-config-skipa/ks';
    // export default ks;

    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,
    ];

    // heavy, selective application such as only in CI or on prepush hook

    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',
    // https://github.com/jsx-eslint/eslint-plugin-react/blob/8cf47a8ac2242ee00ea36eac4b6ae51956ba4411/index.js#L165-L179
    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,
    ];