0.8.3 • Published 10 months ago

@krakentech/eslint-config v0.8.3

Weekly downloads
-
License
ISC
Repository
-
Last release
10 months ago

Introduction

This package provides sensible defaults for configuring ESLint in a project. It is designed around ESLint v9 to allow for easily shareable, modular and extensible configurations by leveraging the new "Flat" config files.

!NOTE This package no longer exports the legacy configuration files. If, for whatever reason, you cannot upgrade, please continue to use version 0.8.3. However, it is strongly recommended that you upgrade because many more (sensible) rules have been added since then.

Installation

We publish our config as an ESLint config on npm. No registry token or authentication is required to install the config. Install using your preferred package manager:

pnpm add -D @krakentech/eslint-config
yarn add -D @krakentech/eslint-config
npm install --save-dev @krakentech/eslint-config

!IMPORTANT This package should be installed at the root of your project. This is particularly important if you are working in a monorepo. The new "Flat" config files are designed to pick which rules apply to which folders/files within your project from the top level.

"Flat" Config files

The new "Flat" config is now the standard in ESLint v9+ and should aim to be adopted by all projects. Now, they must be named eslint.config.js and placed in the root of your project.

This package is a shared config responsible for config and plugin dependencies, not the client project. This design enables plug-and-play configuration of new client projects, which can install @krakentech/eslint-config and forget about linting. However, it is very easy to extend the base configurations so each project can configure itself as needed.

├──src
│  ├── ...
│  ...
├── package.json
└── eslint.config.js <--
├──apps
│  ├──app1
│  │  ├── src
│  │  ├── package.json
│  │   ...
│  ...
├── package.json
└── eslint.config.js <--

Advantages over legacy configs

  • Dynamically load configs
  • No config cascading
  • Improved performance (no more merging)
  • Clearer rule definition
  • Easier to share and extend

Syntax

Currently we only support CommonJS syntax for config files. The default export of the package provides every base config, several utility configs, recommended configs and a load function which can be used to merge multiple configs together.

type LinterConfig = {
  load: (...configs: ConfigWithExtends[]) => FlatConfig.ConfigArray;
  configs: {
    base: Record<string, FlatConfig.Config>;
    recommended: Record<string, ConfigWithExtends>;
    utils: {
      typescript: Record<string, FlatConfig.Config>;
      imports: Record<string, FlatConfig.Config>;
    };
  };
};
  • load: Function which loads a set of configs and merges them into a single config object which ESlint is able to parse.
  • configs: See Configs.

The load function is used to merge multiple configs together. It takes any number of arguments, all of which are required to be ConfigWithExtends objects. We export numerous "base" configs which can be merged with a files definition to apply those rules to those files. For example, to apply Javascript rules to only Javascript files:

// eslint.config.js
const linters = require("@krakentech/eslint-config");

module.exports = linters.load({
  files: ["**/*.{js,jsx,mjs,cjs}"],
  ...linters.configs.base.javascript,
});

Then, if you wanted to apply the recommended Typescript rules to all Typescript files, you could do so like so:

// eslint.config.js
const linters = require("@krakentech/eslint-config");

module.exports = linters.load(
  {
    files: ["**/*.{js,jsx,mjs,cjs}"],
    ...linters.configs.base.javascript,
  },
  {
    files: ["**/*.{ts,tsx,mts,cts}"],
    ...linters.configs.recommended.typescript,
  },
);

If you want to apply multiple configs to the same file set, you have to use the extends property:

// eslint.config.js
const linters = require("@krakentech/eslint-config");

module.exports = linters.load({
  files: ["**/*.{ts,tsx,mts,cts}"],
  extends: [
    linters.configs.base.javascript,
    linters.configs.base.typescript,
    linters.configs.base.imports,
    linters.configs.base.jsxA11y,
    linters.configs.recommended.react,
  ],
});
interface Config {
  /**
   * An string to identify the configuration object. Used in error messages and inspection tools.
   */
  name?: string;
  /**
   * An array of glob patterns indicating the files that the configuration object should apply to.
   * If not specified, the configuration object applies to all files matched by any other configuration object.
   */
  files?: (string | string[])[];
  /**
   * An array of glob patterns indicating the files that the configuration object should not apply to.
   * If not specified, the configuration object applies to all files matched by files.
   */
  ignores?: string[];
  /**
   * An object containing settings related to how JavaScript is configured for linting.
   */
  languageOptions?: LanguageOptions;
  /**
   * An object containing settings related to the linting process.
   */
  linterOptions?: LinterOptions;
  /**
   * An object containing a name-value mapping of plugin names to plugin objects.
   * When `files` is specified, these plugins are only available to the matching files.
   */
  plugins?: Plugins;
  /**
   * Either an object containing `preprocess()` and `postprocess()` methods or
   * a string indicating the name of a processor inside of a plugin
   * (i.e., `"pluginName/processorName"`).
   */
  processor?: string | Processor;
  /**
   * An object containing the configured rules.
   * When `files` or `ignores` are specified, these rule configurations are only available to the matching files.
   */
  rules?: Rules;
  /**
   * An object containing name-value pairs of information that should be available to all rules.
   */
  settings?: Settings;
}

export interface ConfigWithExtends extends Config {
  extends?: Config[];
}

Example Configurations

This package exports a number of recommended configs that can be used to configure common project types. However, it also provides the base configs to allow users to create modular configs themselves using extends.

!IMPORTANT Importantly, this package does not define which files will be linted. This is a deliberate choice to allow for flexibility in how you configure your project. If you don't define files, the linters will lint all project files which may affect performance.

Standalone Typescript Project

// eslint.config.js
const linters = require("@krakentech/eslint-config");

module.exports = linters.load(
  // Ignore linting common build files and node_modules
  { ignores: ["**/node_modules/**", "**/dist/**"] },
  // Lint all Typescript files
  {
    files: ["**/*.{ts,tsx,mts,cts}"],
    extends: [
      linters.configs.base.javascript,
      linters.configs.base.typescript,
    ],
    // Rule overrides if necessary
    rules: {
      // Allow @ts-<directive> comments
      "@typescript-eslint/ban-ts-comment": "off",
      ...
    },
  },
  // Only apply Javascript rules to .js files
  {
    files: ["**/*.{js,jsx,mjs,cjs}"],
    ...linters.utils.typescript.javascript,
  },
);

Standalone Typescript with React Project

// eslint.config.js
const linters = require("@krakentech/eslint-config");

module.exports = linters.load(
  { ignores: ["**/node_modules/**", "**/dist/**"] },
  {
    files: ["**/*.{ts,tsx,mts,cts}"],
    extends: [
      linters.configs.base.javascript,
      linters.configs.base.typescript,
      linters.configs.base.imports,
      linters.configs.base.jsxA11y,
      linters.configs.recommended.react,
    ],
    ...
  },
  ...
);

Standalone NextJS Project

// eslint.config.js
const linters = require("@krakentech/eslint-config");

module.exports = linters.load(
  { ignores: ["**/node_modules/**", "**/.next/**"] },
  {
    files: ["**/*.{ts,tsx,mts,cts}"],
    extends: [
      linters.configs.base.javascript,
      linters.configs.base.typescript,
      linters.configs.base.imports,
      linters.configs.base.jsxA11y,
      linters.configs.recommended.react,
      linters.configs.recommended.next,
    ],
    // Other rules
    ...
  },
  // Other configs
  ...
);
// eslint.config.js
const linters = require("@krakentech/eslint-config");

module.exports = linters.load(
  { ignores: ["**/node_modules/**", "**/.next/**"] },
  {
    files: ["**/*.{ts,tsx,mts,cts}"],
    ...linters.configs.recommended.next,
  },
);

Monorepo

In a monorepo, you probably don't want to lint all of your project files the same way. For example, a typical Typescript package will not need React or NextJS rules, but the frontend application will:

// eslint.config.js
const linters = require("@krakentech/eslint-config");

module.exports = linters.load(
  { ignores: ["**/node_modules/**", "**/.next/**", "**/dist/**"] },
  // NextJS projects
  {
    files: ["apps/next-apps/**/*.{ts,tsx,mts,cts}"],
    ...linters.configs.recommended.next,
  },
  // Apply Typescript, jsxA11y and react rules to vanilla React projects
  {
    files: ["apps/react/**/*.{ts,tsx,mts,cts}"],
    extends: [
      linters.configs.recommended.typescript,
      linters.configs.recommended.jsxA11y,
      linters.configs.recommended.react,
    ],
  },
  // Apply Typescript rules to all packages
  {
    files: ["packages/**/*.{ts,tsx,mts,cts}"],
    ...linters.configs.recommended.typescript,
  },
);

Recommended Configs

We provide a number of "recommended" configs which are intended to be used as a "no configuration" baseline for projects.

Typescript Recommended

The recommended Typesceipt config loads the Javascript, Typescript and Imports configs. This is suitable for projects using Typescript not using React.

// eslint.config.js

module.exports = linters.load(
  ...
  {
    files: ["**/*.{js,jsx,mjs,cjs}"],
    ...linters.configs.recommended.typescript,
  },
  ...
};

NextJS Recommended

The NextJS config enables everything found in the Recommended Typescript config, plus the following:

// eslint.config.js

module.exports = linters.load(
  ...
  {
    files: ["**/*.{js,jsx,mjs,cjs}"],
    ...linters.configs.recommended.next,
  },
  ...
};

Base Configs

Javascript Base

linters.configs.base.javascript

The Javascript package uses the eslint:recommended rule set.

  "constructor-super": "error",
  "for-direction": "error",
  "getter-return": "error",
  "no-async-promise-executor": "error",
  "no-case-declarations": "error",
  "no-class-assign": "error",
  "no-compare-neg-zero": "error",
  "no-cond-assign": "error",
  "no-const-assign": "error",
  "no-constant-binary-expression": "error",
  "no-constant-condition": "error",
  "no-control-regex": "error",
  "no-debugger": "error",
  "no-delete-var": "error",
  "no-dupe-args": "error",
  "no-dupe-class-members": "error",
  "no-dupe-else-if": "error",
  "no-dupe-keys": "error",
  "no-duplicate-case": "error",
  "no-empty": "error",
  "no-empty-character-class": "error",
  "no-empty-pattern": "error",
  "no-empty-static-block": "error",
  "no-ex-assign": "error",
  "no-extra-boolean-cast": "error",
  "no-fallthrough": "error",
  "no-func-assign": "error",
  "no-global-assign": "error",
  "no-import-assign": "error",
  "no-invalid-regexp": "error",
  "no-irregular-whitespace": "error",
  "no-loss-of-precision": "error",
  "no-misleading-character-class": "error",
  "no-new-native-nonconstructor": "error",
  "no-nonoctal-decimal-escape": "error",
  "no-obj-calls": "error",
  "no-octal": "error",
  "no-prototype-builtins": "error",
  "no-redeclare": "error",
  "no-regex-spaces": "error",
  "no-self-assign": "error",
  "no-setter-return": "error",
  "no-shadow-restricted-names": "error",
  "no-sparse-arrays": "error",
  "no-this-before-super": "error",
  "no-undef": "error",
  "no-unexpected-multiline": "error",
  "no-unreachable": "error",
  "no-unsafe-finally": "error",
  "no-unsafe-negation": "error",
  "no-unsafe-optional-chaining": "error",
  "no-unused-labels": "error",
  "no-unused-private-class-members": "error",
  "no-unused-vars": "error",
  "no-useless-backreference": "error",
  "no-useless-catch": "error",
  "no-useless-escape": "error",
  "no-with": "error",
  "require-yield": "error",
  "use-isnan": "error",
  "valid-typeof": "error"

Typescript Base

linters.configs.base.typescript

The Typescript base disables number of Javascript rules which overlap with Typescript and enables the Strict TypeChecked rules

  'constructor-super': 'off', // ts(2335) & ts(2377)
  'getter-return': 'off', // ts(2378)
  'no-const-assign': 'off', // ts(2588)
  'no-dupe-args': 'off', // ts(2300)
  'no-dupe-class-members': 'off', // ts(2393) & ts(2300)
  'no-dupe-keys': 'off', // ts(1117)
  'no-func-assign': 'off', // ts(2630)
  'no-import-assign': 'off', // ts(2632) & ts(2540)
  // TODO - remove this once we no longer support ESLint v8
  'no-new-symbol': 'off', // ts(7009)
  'no-new-native-nonconstructor': 'off', // ts(7009)
  'no-obj-calls': 'off', // ts(2349)
  'no-redeclare': 'off', // ts(2451)
  'no-setter-return': 'off', // ts(2408)
  'no-this-before-super': 'off', // ts(2376) & ts(17009)
  'no-undef': 'off', // ts(2304) & ts(2552)
  'no-unreachable': 'off', // ts(7027)
  'no-unsafe-negation': 'off', // ts(2365) & ts(2322) & ts(2358)
  'no-var': 'error', // ts transpiles let/const to var, so no need for vars any more
  'prefer-const': 'error', // ts provides better types with const
  'prefer-rest-params': 'error', // ts provides better types with rest args over arguments
  'prefer-spread': 'error', // ts transpiles spread to apply, so no need for manual apply
'@typescript-eslint/await-thenable': 'error',
    '@typescript-eslint/ban-ts-comment': [
      'error',
      { minimumDescriptionLength: 10 },
    ],
    'no-array-constructor': 'off',
    '@typescript-eslint/no-array-constructor': 'error',
    '@typescript-eslint/no-array-delete': 'error',
    '@typescript-eslint/no-base-to-string': 'error',
    '@typescript-eslint/no-confusing-void-expression': 'error',
    '@typescript-eslint/no-deprecated': 'error',
    '@typescript-eslint/no-duplicate-enum-values': 'error',
    '@typescript-eslint/no-duplicate-type-constituents': 'error',
    '@typescript-eslint/no-dynamic-delete': 'error',
    '@typescript-eslint/no-empty-object-type': 'error',
    '@typescript-eslint/no-explicit-any': 'error',
    '@typescript-eslint/no-extra-non-null-assertion': 'error',
    '@typescript-eslint/no-extraneous-class': 'error',
    '@typescript-eslint/no-floating-promises': 'error',
    '@typescript-eslint/no-for-in-array': 'error',
    'no-implied-eval': 'off',
    '@typescript-eslint/no-implied-eval': 'error',
    '@typescript-eslint/no-invalid-void-type': 'error',
    '@typescript-eslint/no-meaningless-void-operator': 'error',
    '@typescript-eslint/no-misused-new': 'error',
    '@typescript-eslint/no-misused-promises': 'error',
    '@typescript-eslint/no-mixed-enums': 'error',
    '@typescript-eslint/no-namespace': 'error',
    '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error',
    '@typescript-eslint/no-non-null-asserted-optional-chain': 'error',
    '@typescript-eslint/no-non-null-assertion': 'error',
    '@typescript-eslint/no-redundant-type-constituents': 'error',
    '@typescript-eslint/no-require-imports': 'error',
    '@typescript-eslint/no-this-alias': 'error',
    '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
    '@typescript-eslint/no-unnecessary-condition': 'error',
    '@typescript-eslint/no-unnecessary-template-expression': 'error',
    '@typescript-eslint/no-unnecessary-type-arguments': 'error',
    '@typescript-eslint/no-unnecessary-type-assertion': 'error',
    '@typescript-eslint/no-unnecessary-type-constraint': 'error',
    '@typescript-eslint/no-unnecessary-type-parameters': 'error',
    '@typescript-eslint/no-unsafe-argument': 'error',
    '@typescript-eslint/no-unsafe-assignment': 'error',
    '@typescript-eslint/no-unsafe-call': 'error',
    '@typescript-eslint/no-unsafe-declaration-merging': 'error',
    '@typescript-eslint/no-unsafe-enum-comparison': 'error',
    '@typescript-eslint/no-unsafe-function-type': 'error',
    '@typescript-eslint/no-unsafe-member-access': 'error',
    '@typescript-eslint/no-unsafe-return': 'error',
    '@typescript-eslint/no-unsafe-unary-minus': 'error',
    'no-unused-expressions': 'off',
    '@typescript-eslint/no-unused-expressions': 'error',
    'no-unused-vars': 'off',
    '@typescript-eslint/no-unused-vars': 'error',
    'no-useless-constructor': 'off',
    '@typescript-eslint/no-useless-constructor': 'error',
    '@typescript-eslint/no-wrapper-object-types': 'error',
    'no-throw-literal': 'off',
    '@typescript-eslint/only-throw-error': 'error',
    '@typescript-eslint/prefer-as-const': 'error',
    '@typescript-eslint/prefer-literal-enum-member': 'error',
    '@typescript-eslint/prefer-namespace-keyword': 'error',
    'prefer-promise-reject-errors': 'off',
    '@typescript-eslint/prefer-promise-reject-errors': 'error',
    '@typescript-eslint/prefer-reduce-type-parameter': 'error',
    '@typescript-eslint/prefer-return-this-type': 'error',
    'require-await': 'off',
    '@typescript-eslint/require-await': 'error',
    '@typescript-eslint/restrict-plus-operands': [
      'error',
      {
        allowAny: false,
        allowBoolean: false,
        allowNullish: false,
        allowNumberAndString: false,
        allowRegExp: false,
      },
          ],
          '@typescript-eslint/restrict-template-expressions': [
      'error',
      {
        allowAny: false,
        allowBoolean: false,
        allowNullish: false,
        allowNumber: false,
        allowRegExp: false,
        allowNever: false,
      },
    ],
    'no-return-await': 'off',
    '@typescript-eslint/return-await': [
      'error',
      'error-handling-correctness-only',
    ],
    '@typescript-eslint/triple-slash-reference': 'error',
    '@typescript-eslint/unbound-method': 'error',
    '@typescript-eslint/unified-signatures': 'error',
    '@typescript-eslint/use-unknown-in-catch-callback-variable': 'error',

!TIP By default this enables type checked rules. If you are experiencing performance issues, you can disable those rules using the Disable Type Checked utility config.


Import and Simple Import Sort

linters.configs.base.imports

To lint imports we use the following:

Only three import rules are enabled:

  • import/first - Make sure imports are at the top of the file
  • import/newline-after-import - Ensure a newline after imports
  • import/no-duplicates - Ensure no duplicate imports

Then the simple-import-sort plugin groups and sorts the imports alphabetically. This plugin enables fixing to auto-sort imports.


React

linters.configs.base.react

The react package enables the following:

We generally use the recommended settings, but disable rules that are not necessary with Typescript or functional components.

  "react/display-name": "error",
  "react/jsx-key": "error",
  "react/jsx-no-comment-textnodes": "error",
  "react/jsx-no-duplicate-props": "error",
  "react/jsx-no-target-blank": "error",
  "react/jsx-no-undef": "error",
  "react/jsx-uses-react": "error",
  "react/jsx-uses-vars": "error",
  "react/no-children-prop": "error",
  "react/no-danger-with-children": "error",
  "react/no-deprecated": "error",
  "react/no-direct-mutation-state": "error",
  "react/no-find-dom-node": "error",
  "react/no-is-mounted": "error",
  "react/no-render-return-value": "error",
  "react/no-string-refs": "error",
  "react/no-unescaped-entities": "error",
  "react/no-unknown-property": "error",
  "react/no-unsafe": "off",
  // This is not necessary with typescript
  "react/prop-types": "off",
  // This is not necessary
  "react/react-in-jsx-scope": "off",
  "react/require-render-return": "error",
  "react-hooks/rules-of-hooks": "error",
  // Warn, because sometimes you don't want every dependency to trigger a re-render
  "react-hooks/exhaustive-deps": "warn",

JSX A11y

linters.configs.base.jsxA11y

When using jsx (typically with React) we should be using jsA1lly to help enable common accessibility rules. This package enables the recommended settings of following:

  'jsx-a11y/alt-text': 'error',
  'jsx-a11y/anchor-ambiguous-text': 'off', // TODO: error
  'jsx-a11y/anchor-has-content': 'error',
  'jsx-a11y/anchor-is-valid': 'error',
  'jsx-a11y/aria-activedescendant-has-tabindex': 'error',
  'jsx-a11y/aria-props': 'error',
  'jsx-a11y/aria-proptypes': 'error',
  'jsx-a11y/aria-role': 'error',
  'jsx-a11y/aria-unsupported-elements': 'error',
  'jsx-a11y/autocomplete-valid': 'error',
  'jsx-a11y/click-events-have-key-events': 'error',
  'jsx-a11y/control-has-associated-label': [
    'off',
    {
      ignoreElements: [
        'audio',
        'canvas',
        'embed',
        'input',
        'textarea',
        'tr',
        'video',
      ],
      ignoreRoles: [
        'grid',
        'listbox',
        'menu',
        'menubar',
        'radiogroup',
        'row',
        'tablist',
        'toolbar',
        'tree',
        'treegrid',
      ],
      includeRoles: ['alert', 'dialog'],
          },
        ],
        'jsx-a11y/heading-has-content': 'error',
        'jsx-a11y/html-has-lang': 'error',
        'jsx-a11y/iframe-has-title': 'error',
        'jsx-a11y/img-redundant-alt': 'error',
        'jsx-a11y/interactive-supports-focus': [
          'error',
          {
      tabbable: [
        'button',
        'checkbox',
        'link',
        'searchbox',
        'spinbutton',
        'switch',
        'textbox',
      ],
    },
  ],
  'jsx-a11y/label-has-associated-control': 'error',
  'jsx-a11y/label-has-for': 'off',
  'jsx-a11y/media-has-caption': 'error',
  'jsx-a11y/mouse-events-have-key-events': 'error',
  'jsx-a11y/no-access-key': 'error',
  'jsx-a11y/no-autofocus': 'error',
  'jsx-a11y/no-distracting-elements': 'error',
  'jsx-a11y/no-interactive-element-to-noninteractive-role': [
    'error',
    {
      tr: ['none', 'presentation'],
      canvas: ['img'],
    },
  ],
  'jsx-a11y/no-noninteractive-element-interactions': [
    'error',
    {
      handlers: [
        'onClick',
        'onError',
        'onLoad',
        'onMouseDown',
        'onMouseUp',
        'onKeyPress',
        'onKeyDown',
        'onKeyUp',
      ],
      alert: ['onKeyUp', 'onKeyDown', 'onKeyPress'],
      body: ['onError', 'onLoad'],
      dialog: ['onKeyUp', 'onKeyDown', 'onKeyPress'],
      iframe: ['onError', 'onLoad'],
      img: ['onError', 'onLoad'],
    },
  ],
  'jsx-a11y/no-noninteractive-element-to-interactive-role': [
    'error',
    {
      ul: [
        'listbox',
        'menu',
        'menubar',
        'radiogroup',
        'tablist',
        'tree',
        'treegrid',
      ],
      ol: [
        'listbox',
        'menu',
        'menubar',
        'radiogroup',
        'tablist',
        'tree',
        'treegrid',
      ],
      li: [
        'menuitem',
        'menuitemradio',
        'menuitemcheckbox',
        'option',
        'row',
        'tab',
        'treeitem',
      ],
      table: ['grid'],
      td: ['gridcell'],
      fieldset: ['radiogroup', 'presentation'],
          },
        ],
        'jsx-a11y/no-noninteractive-tabindex': [
          'error',
          {
      tags: [],
      roles: ['tabpanel'],
      allowExpressionValues: true,
    },
  ],
  'jsx-a11y/no-redundant-roles': 'error',
  'jsx-a11y/no-static-element-interactions': [
    'error',
    {
      allowExpressionValues: true,
      handlers: [
        'onClick',
        'onMouseDown',
        'onMouseUp',
        'onKeyPress',
        'onKeyDown',
        'onKeyUp',
      ],
    },
  ],
  'jsx-a11y/role-has-required-aria-props': 'error',
  'jsx-a11y/role-supports-aria-props': 'error',
  'jsx-a11y/scope': 'error',
  'jsx-a11y/tabindex-no-positive': 'error',
};

Next

linters.configs.base.next

When using a NextJS project we should enable the following to make the most of NextJS's features:

  '@next/next/google-font-display': 'warn',
  '@next/next/google-font-preconnect': 'warn',
  '@next/next/next-script-for-ga': 'warn',
  '@next/next/no-async-client-component': 'warn',
  '@next/next/no-before-interactive-script-outside-document': 'warn',
  '@next/next/no-css-tags': 'warn',
  '@next/next/no-head-element': 'warn',
  '@next/next/no-html-link-for-pages': 'warn',
  '@next/next/no-img-element': 'warn',
  '@next/next/no-page-custom-font': 'warn',
  '@next/next/no-styled-jsx-in-document': 'warn',
  '@next/next/no-sync-scripts': 'warn',
  '@next/next/no-title-in-document-head': 'warn',
  '@next/next/no-typos': 'warn',
  '@next/next/no-unwanted-polyfillio': 'warn',
  // errors
  '@next/next/inline-script-id': 'error',
  '@next/next/no-assign-module-variable': 'error',
  '@next/next/no-document-import-in-page': 'error',
  '@next/next/no-duplicate-head': 'error',
  '@next/next/no-head-import-in-document': 'error',
  '@next/next/no-script-component-in-head': 'error',

Prettier

linters.configs.base.prettier

!IMPORTANT Prettier is disabled by default. The official advice by both the Prettier team and the ESLint team is to not integrate Prettier rules into your ESLint config. Formatters should be something you forget about; lots of red squiggly lines in your editor can be distracting as well as less performant.

However, it is expected that users implement Prettier into their workflow at some point. It is recommended that users integrate Prettier into their editors, or you run Prettier as a file watcher.

Alternatively (or in addition to), you may also conditionally enable Prettier in your ESLint config during CI to ensure committed code is formatted correctly. This can be achieved like so:

// eslint.config.js

const isCI = process.env.CI;

module.exports = linters.load(
  ...
  // Enable Prettier in CI only
  isCI && {
    // No files array will lint all files
    ...linters.configs.base.prettier,
  },
  ...
};

Utils

We provide a number of "utility" configs which are used to solve particular problems.

Disable Type Checked

If you need to disable TypeScript completely for a particular set of files, you can use the disableTypeChecked util.

// eslint.config.js

module.exports = linters.load(
  ...
  {
    files: ["packages/js-pkg/**/*.{js,jsx,mjs,cjs}"],
    ...linters.configs.utils.typescript.disableTypeChecked,
  },
  ...
};

Performance

The Typescript config includes the full set of type checking rules. This can be very useful as the linter is able to take advantage of the Typescript APIs to provider deeper insights into your code. However, this can come at a cost of performance as it requires Type transpilation.

We provide a "performance" utility which can be used to disable some of the more egregious rules which can slow down linting (hopefully) without compromising on linting quality too much.

// eslint.config.js

module.exports = linters.load(
  ...
  {
    files: ["**/*.{ts,tsx,mts,cts}"],
    ...linters.configs.utils.typescript.improvePerformance,
  },
  ...
};

Disable Import Sort

!NOTE The imports config with simple-import-sort is enabled by default because it provides sensible defaults that can be fixed automatically. However, to help projects incrementally adopt the new package we provide the ability to disable sorting. Eventually, this will be removed to ensure import sorting is always enabled.

This utility disables the simple-import-sort plugin (it leaves the import rules on):

// eslint.config.js

module.exports = linters.load(
  ...
  {
    files: ["**/*.{ts,tsx,mts,cts}"],
    ...linters.configs.utils.imports.disableSort,
  },
  ...
};

Dependabot

If Dependabot is configured on your project, it will create Pull Requests to update @krakentech/eslint-config automatically when we update it, so you never need to worry about linting dependencies again.

Releasing updates

Following instructions for @changesets/cli;

Whenever you make a change, run pnpm changeset to generate documentation and include this in your last commit.

When you're ready to release changes (not necessarily after each change), follow the documentation from the changesets package above.

You may need to create a .npmrc file in packages/eslint-config containing the following. The authToken can be found under "NPMJS - Global Write access token" in the KT - Vendors 1Password entry:

@krakentech:registry=https://registry.npmjs.org/
//registry.npmjs.org/:_authToken=WRITE_ACCESS_TOKEN_FROM_1PASSWORD

To use your new release,

  • Make a tea or coffee first, as npm takes a few minutes to propagate
  • Use yarn add -D @krakentech/eslint-config@x.y.z to install the new release

If you have trouble, check the package hasn't been made private inadvertently. Logging into our npm organisation account is required to fix this if broken.