WeAreGenki/minna-ui

View on GitHub
utils/eslint-config/index.js

Summary

Maintainability
A
35 mins
Test Coverage
/**
 * Minna UI base ESLint config preset.
 *
 * @file Base ESLint configuration including parsing of various file types, a
 * somewhat opinionated set of base rules, and file overrides to automate
 * applying the different rules for different files (based on file name).
 *
 * This preset makes use of file extensions to infer the file type. Make sure
 * you use the correct extension otherwise you may get incorrect lint feedback.
 *
 * TIP: If you have a TypeScript project, use the `typed` add-on config. It
 * requires extra set up steps which are outlined in the config file.
 *
 * MAINTAINERS: To debug rule performance, use a `TIMING=1` environment
 * variable, e.g. `TIMING=1 yarn eslint ...`. ESLint will print a table with
 * timing stats and highlight the slowest rules.
 *
 * @see https://eslint.org/docs/user-guide/configuring
 */

/* eslint-disable @typescript-eslint/no-magic-numbers, sort-keys */

'use strict';

const { join } = require('path');
const jestConfig = require('./jest.js');
const legacyConfig = require('./legacy.js');
const nodeJsConfig = require('./node-js.js');
const nodeTsConfig = require('./node-ts.js');

const OFF = 0;
const WARNING = 1;
const ERROR = 2;

/** @type {import('./types').ESLintConfig} */
module.exports = {
  extends: [
    'eslint:recommended',
    'airbnb-base',
    'plugin:jsdoc/recommended',
    'plugin:security/recommended',
    'plugin:@typescript-eslint/eslint-recommended',
    'plugin:@typescript-eslint/recommended',
  ],
  plugins: [
    'import',
    'html',
    'markdown',
    'jsdoc',
    'security',
    '@typescript-eslint',
  ],
  parser: '@typescript-eslint/parser',
  env: {
    browser: true,
    es6: true,
  },
  reportUnusedDisableDirectives: true,
  settings: {
    'html/indent': '+2',
    'html/report-bad-indent': ERROR,
    'html/xml-extensions': ['.svg', '.xhtml', 'xml'],
    'import/cache': Infinity, // Only OK when not using long running processes e.g. eslint-loader
    'import/extensions': [
      '.mjs',
      '.js',
      '.ts',
      '.svelte',
      '.jsx',
      '.tsx',
      '.d.ts',
    ],
    'import/ignore': ['.css', '.pcss', '.svelte'],
    'import/resolver': {
      '@minna-ui/eslint-import-resolver': {
        alias: {
          '^##\\/(.*)$': join(process.cwd(), 'src/$1'),
        },
        extensions: [
          '.mjs',
          '.js',
          '.ts',
          '.svelte',
          '.jsx',
          '.tsx',
          '.json',
          '.css',
          '.pcss',
          '.node',
          '.d.ts',
        ],
      },
    },
    jsdoc: {
      mode: 'typescript',
    },
  },
  rules: {
    /* eslint-enable sort-keys */
    '@typescript-eslint/ban-ts-ignore': WARNING,
    '@typescript-eslint/ban-types': [
      ERROR,
      {
        types: {
          Array: 'Use [] instead',
          Object: 'Use object or {} instead',
          String: {
            fixWith: 'string',
            message: 'Use string instead',
          },
        },
      },
    ],
    '@typescript-eslint/explicit-function-return-type': [
      ERROR,
      {
        allowExpressions: true,
        allowHigherOrderFunctions: true,
        allowTypedFunctionExpressions: true,
      },
    ],
    '@typescript-eslint/explicit-member-accessibility': [
      ERROR,
      { accessibility: 'no-public' },
    ],
    '@typescript-eslint/indent': [
      ERROR,
      2,
      {
        // ESTree spec: https://github.com/estree/estree
        // TS node types: https://git.io/fj6bE
        ignoredNodes: ['ConditionalExpression *'], // Prettier :'(
        SwitchCase: 1,
      },
    ],
    '@typescript-eslint/member-ordering': ERROR,
    '@typescript-eslint/no-empty-function': ERROR,
    '@typescript-eslint/no-empty-interface': [
      ERROR,
      { allowSingleExtends: true },
    ],
    '@typescript-eslint/no-extra-semi': ERROR,
    '@typescript-eslint/no-extraneous-class': ERROR,
    '@typescript-eslint/no-magic-numbers': [
      WARNING,
      {
        detectObjects: false,
        enforceConst: false,
        ignore: [-1, 0, 1],
        ignoreArrayIndexes: true,
        ignoreEnums: true,
        ignoreNumericLiteralTypes: true,
        ignoreReadonlyClassProperties: true,
      },
    ],
    '@typescript-eslint/no-this-alias': ERROR,
    '@typescript-eslint/no-unused-vars': [
      WARNING,
      {
        args: 'none',
        ignoreRestSiblings: true,
      },
    ],
    '@typescript-eslint/no-useless-constructor': ERROR,
    '@typescript-eslint/prefer-for-of': WARNING,
    '@typescript-eslint/prefer-function-type': WARNING,
    '@typescript-eslint/unified-signatures': ERROR,
    'comma-dangle': [
      ERROR,
      {
        arrays: 'always-multiline',
        exports: 'always-multiline',
        functions: 'only-multiline', // On multiline function params is OK 👌
        imports: 'always-multiline',
        objects: 'always-multiline',
      },
    ],
    'id-length': [ERROR, { exceptions: ['_'], min: 2 }], // Encourage descriptive names
    'import/extensions': [
      ERROR,
      'always',
      {
        js: 'ignorePackages',
        jsx: 'ignorePackages',
        mjs: 'ignorePackages',
        ts: 'never',
        tsx: 'never',
      },
    ],
    // FIXME: Enable after issue is resolved: https://github.com/typescript-eslint/typescript-eslint/issues/389
    // 'import/no-deprecated': WARNING,
    'import/prefer-default-export': OFF,
    indent: OFF, // Handled by `@typescript-eslint/indent`
    // FIXME: Enable after issue is resolved: https://github.com/typescript-eslint/typescript-eslint/issues/389
    // 'jsdoc/check-examples': [WARNING, { matchingFileName: 'example.md' }],
    'jsdoc/check-examples': OFF,
    'jsdoc/check-indentation': WARNING,
    'jsdoc/check-tag-names': [
      WARNING,
      { definedTags: ['externs', 'jest-environment', 'jsx'] },
    ],
    'jsdoc/require-description-complete-sentence': WARNING,
    'jsdoc/require-hyphen-before-param-description': WARNING,
    'jsdoc/require-jsdoc': OFF, // Far too annoying
    'jsdoc/require-returns': [WARNING, { forceReturnsWithAsync: true }],
    'max-classes-per-file': WARNING,
    'max-len': [
      ERROR,
      {
        code: 80, // Consistency with prettier
        ignorePattern:
          'eslint-disable|eslint-enable|@ts-ignore|stylelint-disable|@typedef',
        ignoreRegExpLiterals: true,
        ignoreStrings: true,
        ignoreTemplateLiterals: true,
        ignoreTrailingComments: true,
        ignoreUrls: true,
      },
    ],
    'no-console': ERROR,
    'no-debugger': ERROR,
    'no-empty': [ERROR, { allowEmptyCatch: true }],
    'no-extra-semi': OFF, // Handled by `@typescript-eslint/no-extra-semi`
    'no-magic-numbers': OFF, // Handled by `@typescript-eslint/no-magic-numbers`
    'no-return-assign': [ERROR, 'except-parens'],
    'no-useless-constructor': OFF, // Handled by `@typescript-eslint/no-useless-constructor`
    'object-curly-newline': [ERROR, { consistent: true }],
    'sort-keys': [WARNING, 'asc', { caseSensitive: false, natural: true }],
    'spaced-comment': [
      ERROR,
      'always',
      {
        block: {
          balanced: true,
          exceptions: ['*'],
          markers: ['!'], // Immutable comments
        },
        line: {
          markers: ['/'], // TypeScript triple slash directives
        },
      },
    ],
    /* eslint-disable sort-keys */

    // Rules incompatible with prettier :'(
    'arrow-parens': OFF,
    'function-paren-newline': OFF,
    'implicit-arrow-linebreak': OFF,
    'operator-linebreak': OFF,

    // Rules which are too slow
    // TODO: Consider removing after issue is resolved:
    // - https://github.com/typescript-eslint/typescript-eslint/issues/389
    // - https://github.com/benmosher/eslint-plugin-import/pull/1409
    'import/default': OFF,
    'import/export': OFF,
    'import/named': OFF,
    'import/namespace': OFF,
    'import/no-cycle': OFF,
    'import/no-deprecated': OFF,
    'import/no-named-as-default-member': OFF,
    'import/no-named-as-default': OFF,
    'import/no-unused-modules': OFF,
  },

  overrides: [
    // JavaScript
    {
      files: ['*.js', '*.jsx'],
      parserOptions: nodeJsConfig.parserOptions,
      rules: nodeJsConfig.rules,
    },
    {
      files: ['*.js', '*.jsx', '*.mjs'],
      rules: {
        '@typescript-eslint/explicit-function-return-type': OFF,
        '@typescript-eslint/explicit-member-accessibility': OFF,
      },
    },

    // ES module files
    {
      files: ['*.mjs', 'preact.config.js', 'rollup.config.js'],
      parserOptions: {
        sourceType: 'module',
      },
      env: {
        commonjs: false,
        node: true, // It's uncommon to use ESM in browsers
      },
      rules: {
        '@typescript-eslint/no-require-imports': ERROR,
      },
    },

    // JSX
    {
      files: ['*.jsx', '*.tsx'],
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
      },
    },

    // TypeScript
    {
      files: ['*.ts', '*.tsx'],
      parserOptions: nodeTsConfig.parserOptions,
      rules: nodeTsConfig.rules,
    },

    // TypeScript declaration files
    {
      files: ['*.d.ts'],
      rules: {
        '@typescript-eslint/no-extraneous-class': OFF,
        'import/no-extraneous-dependencies': [ERROR, { devDependencies: true }],
        'no-useless-constructor': OFF, // Crashes node process
        'no-var': OFF,
        'vars-on-top': OFF,
      },
    },

    // Config files
    {
      files: ['*.config.js', '*rc.js'],
      excludedFiles: ['preact.config.js', 'rollup.config.js'], // Use ES modules
      parserOptions: {
        sourceType: 'script',
      },
      env: {
        commonjs: true,
        node: true,
      },
      rules: {
        // Can use any installed dependency
        'import/no-extraneous-dependencies': [
          ERROR,
          {
            devDependencies: true,
            peerDependencies: true,
          },
        ],
      },
    },

    // Unit tests
    {
      files: [
        '__mocks__/*',
        '__tests__/*',
        '*.spec.js',
        '*.spec.mjs',
        '*.spec.ts',
        '*.test.js',
        '*.test.jsx',
        '*.test.mjs',
        '*.test.ts',
        '*.test.tsx',
      ],
      ...jestConfig,
    },

    // Raw HTML (without transpiling)
    {
      files: ['*.html'],
      parserOptions: {
        // https://github.com/BenoitZugmeyer/eslint-plugin-html#multiple-scripts-tags-in-a-html-file
        sourceType: 'module',
      },
      env: {
        browser: true,
        commonjs: false,
        es6: false,
        node: false,
      },
      rules: legacyConfig.rules,
    },

    // Auto-generated declarations
    {
      files: ['*.css.d.ts', '*.pcss.d.ts'],
      rules: {
        '@typescript-eslint/interface-name-prefix': OFF,
        '@typescript-eslint/member-delimiter-style': OFF,
      },
    },

    // Markdown documentation files
    {
      files: ['*.md'],
      parserOptions: {
        ecmaFeatures: {
          impliedStrict: true,
        },
      },
      rules: {
        // Disable rules that don't make sense in code snippets
        '@typescript-eslint/no-var-requires': OFF,
        'import/no-extraneous-dependencies': OFF,
        'import/no-unresolved': OFF,
        'no-console': OFF,
        strict: OFF,
      },
    },
  ],
};