Files
pezkuwi-dev/packages/dev/config/eslint.rules.js
Satoshi Qazi Muhammed f9f04f6adc Move the toolchain to eslint 10, typescript-eslint 8 and current plugins
eslint 8 and typescript-eslint 6 are past their support, and the webpack
dev server chain carried the dependency alerts every repository using this
package inherited. Upstream @polkadot/dev still pins the same versions, so
this leaves it on purpose.

The rules are carried over with their options, so the lint means what it
meant:

- eslint-config-standard 17 (written for eslint 8) is replaced by its 161
  rules in config/eslint.standard.js, with their options. Its 48 formatting
  rules, deprecated in eslint core, are the same rules in @stylistic;
  no-new-object and no-new-symbol are no-object-constructor and
  no-new-native-nonconstructor; import/* is import-x/*.
- eslint-plugin-import (eslint <= 9) is eslint-plugin-import-x;
  eslint-plugin-deprecation is @typescript-eslint/no-deprecated; the
  formatting rules of core, typescript-eslint and eslint-plugin-react used
  in eslint.rules.js are their @stylistic versions with the same options.
- typescript-eslint 8's preset swaps core no-unused-expressions for its own
  with default options; standard's (short circuit, ternary and tagged
  templates allowed) are carried over to it.
- eslint-plugin-react and eslint-plugin-header still call context methods
  eslint 10 removed; @eslint/compat's fixupPluginRules supplies them. The
  header rule also gets `schema: false`, as eslint 9+ rejects options for a
  rule with no schema. The original header plugin stays rather than its
  fork: it reports at the first token after leading comments, so a
  generated file's /* eslint-disable */ still covers it, as it always has.

The effective config was compared rule by rule (--print-config) for .ts,
.tsx, .spec.ts and .mjs files before and after. Every rule that left has
its successor active; option differences are eslint 10 spelling out
defaults. New rules come from the eslint 10, typescript-eslint 8 and
react-hooks 7 presets. no-inner-declarations left eslint's preset upstream.

Also current: webpack-dev-server 6, webpack-cli 7, webpack-merge 6, rollup
plugins, madge 8, gh-release 8, globals 17, espree 11; Node >= 20.19 for
the dev package, which eslint 10 requires.

In this repository the new rules found: Function types now named by
no-unsafe-function-type; optional chains, includes() and RegExp#exec where
they say the same thing; createWrapper returns Describe, so its three casts
were redundant; rootCjs.spec casts through unknown, so the lint result does
not depend on whether build/ exists. lint, build and test (149 passed) pass,
and lint leaves the tree unchanged with and without a build.
2026-10-09 08:56:18 +04:00

221 lines
7.3 KiB
JavaScript

// Copyright 2017-2026 @pezkuwi/dev authors & contributors
// SPDX-License-Identifier: Apache-2.0
import JSON5 from 'json5';
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
const FIXME = {
// This is in the new 6.0.0 and we should switch this on
// at some point. For a first iteration we keep as-is
'@typescript-eslint/prefer-nullish-coalescing': 'off'
};
/**
* Returns a copyright header pattern (using tsconfig.base.json)
*
* @returns {string}
*/
function getHeaderPattern () {
const tsPath = path.join(process.cwd(), 'tsconfig.base.json');
if (!fs.existsSync(tsPath)) {
throw new Error(`Unable to load ${tsPath}`);
}
const tsConfig = JSON5.parse(fs.readFileSync(tsPath, 'utf-8'));
if (!tsConfig?.compilerOptions?.paths) {
throw new Error(`Unable to extract compilerOptions.paths structure from ${tsPath}`);
}
const paths = Object.keys(tsConfig.compilerOptions.paths);
if (!paths.length) {
throw new Error(`No keys found in compilerOptions.paths from ${tsPath}`);
}
const packages = paths.reduce((packages, k) => {
const [pd, pk] = k.split('/');
if (pd !== '@pezkuwi' || !pk) {
throw new Error(`Non @pezkuwi path in ${tsPath}`);
}
return packages.length
? `${packages}|${pk}`
: pk;
}, '');
const fullyear = new Date().getFullYear();
const years = [];
for (let i = 17, last = fullyear - 2000; i < last; i++) {
years.push(`${i}`);
}
return ` Copyright 20(${years.join('|')})(-${fullyear})? @pezkuwi/(${packages})( authors & contributors)?`;
}
export const overrideAll = {
...FIXME,
'@stylistic/arrow-parens': ['error', 'always'],
'@stylistic/brace-style': ['error', '1tbs'],
'@stylistic/function-call-argument-newline': ['error', 'consistent'],
'@stylistic/indent': ['error', 2],
'@stylistic/no-extra-semi': 'error',
'@stylistic/object-curly-newline': ['error', {
ExportDeclaration: { minProperties: 2048 },
ImportDeclaration: { minProperties: 2048 },
ObjectPattern: { minProperties: 2048 }
}],
'@stylistic/padding-line-between-statements': [
'error',
{ blankLine: 'always', next: '*', prev: ['const', 'let', 'var'] },
{ blankLine: 'any', next: ['const', 'let', 'var'], prev: ['const', 'let', 'var'] },
{ blankLine: 'always', next: 'block-like', prev: '*' },
{ blankLine: 'always', next: '*', prev: 'block-like' },
{ blankLine: 'always', next: 'function', prev: '*' },
{ blankLine: 'always', next: '*', prev: 'function' },
{ blankLine: 'always', next: 'try', prev: '*' },
{ blankLine: 'always', next: '*', prev: 'try' },
{ blankLine: 'always', next: 'return', prev: '*' },
{ blankLine: 'always', next: 'import', prev: '*' },
{ blankLine: 'always', next: '*', prev: 'import' },
{ blankLine: 'any', next: 'import', prev: 'import' }
],
'@stylistic/semi': ['error', 'always'],
'@stylistic/spaced-comment': ['error', 'always', {
block: {
// pure export helpers
markers: ['#__PURE__']
},
line: {
// TS reference types
markers: ['/ <reference']
}
}],
'@stylistic/type-annotation-spacing': 'error',
// the next 2 enforce isolatedModules & verbatimModuleSyntax
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/dot-notation': 'error',
'@typescript-eslint/no-deprecated': 'error',
'@typescript-eslint/no-non-null-assertion': 'error',
// standard's options for the core rule, which the TypeScript preset turns off
// in favour of this one (with defaults that reject a && b() and a ? b() : c())
'@typescript-eslint/no-unused-expressions': ['error', {
allowShortCircuit: true,
allowTaggedTemplates: true,
allowTernary: true
}],
// ts itself checks and ignores those starting with _, align the linting
'@typescript-eslint/no-unused-vars': ['error', {
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
vars: 'all',
varsIgnorePattern: '^_'
}],
curly: ['error', 'all'],
'default-param-last': 'off', // conflicts with TS version
'dot-notation': 'off', // conflicts with TS version
'func-style': ['error', 'declaration', {
allowArrowFunctions: true
}],
// this does help with declarations, but also
// applies to invocations, which is an issue...
// '@stylistic/function-paren-newline': ['error', 'never'],
'header/header': ['error', 'line', [
{ pattern: getHeaderPattern() },
' SPDX-License-Identifier: Apache-2.0'
], 2],
'import-newlines/enforce': ['error', {
forceSingleLine: true,
items: 2048
}],
'import-x/export': 'error',
'import-x/extensions': ['error', 'ignorePackages', {
cjs: 'always',
js: 'always',
json: 'always',
jsx: 'never',
mjs: 'always',
ts: 'never',
tsx: 'never'
}],
'import-x/first': 'error',
'import-x/newline-after-import': 'error',
'import-x/no-duplicates': 'error',
'import-x/order': 'off', // conflicts with simple-import-sort
'no-unused-vars': 'off',
'no-use-before-define': 'off',
'simple-import-sort/exports': 'error',
'simple-import-sort/imports': ['error', {
groups: [
['^\u0000'], // all side-effects (0 at start)
['\u0000$', '^@pezkuwi.*\u0000$', '^\\..*\u0000$'], // types (0 at end)
// ['^node:'], // node
['^[^/\\.]'], // non-pezkuwi
['^@pezkuwi'], // pezkuwi
['^\\.\\.(?!/?$)', '^\\.\\./?$', '^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'] // local (. last)
]
}],
'sort-destructure-keys/sort-destructure-keys': ['error', {
caseSensitive: true
}],
'sort-keys': 'error'
};
export const overrideJsx = {
'@stylistic/jsx-closing-bracket-location': ['warn', 'tag-aligned'],
'@stylistic/jsx-first-prop-new-line': ['warn', 'multiline-multiprop'],
'@stylistic/jsx-max-props-per-line': ['warn', {
maximum: 1,
when: 'always'
}],
'@stylistic/jsx-newline': ['error', {
prevent: true
}],
'@stylistic/jsx-props-no-multi-spaces': 'error',
'@stylistic/jsx-quotes': ['error', 'prefer-single'],
'@stylistic/jsx-sort-props': ['warn', {
noSortAlphabetically: false
}],
'@stylistic/jsx-tag-spacing': ['error', {
afterOpening: 'never',
beforeClosing: 'never',
beforeSelfClosing: 'always',
closingSlash: 'never'
}],
// swap from recommended warning to error
'react-hooks/exhaustive-deps': 'error',
'react/jsx-fragments': 'error',
'react/jsx-no-bind': 'error',
'react/prop-types': 'off' // this is a completely broken rule
};
export const overrideJs = {
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-require-imports': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@typescript-eslint/restrict-plus-operands': 'off',
'@typescript-eslint/restrict-template-expressions': 'off'
};
export const overrideSpec = {
// in the specs we are a little less worried about
// specific correctness, i.e. we can have dangling bits
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'jest/expect-expect': ['warn', {
assertFunctionNames: ['assert', 'expect']
}]
};