Repository: exadel-inc/esl
Pull request #2968 (2025-03-05 02:15:35)

epic(eslint-config): rework and restructure eslint-config-esl module


Patch:
diff --git a/eslint.config.mjs b/eslint.config.mjs
index fcbee259d..edb61ab97 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -12,7 +12,8 @@ export default [
   ...eslintConfigIgnore,
 
   // Using shared ESL ESLint Config
-  ...lang,
+  ...lang.js,
+  ...lang.ts,
   ...strict,
 
   // ESL ESLint Plugin
diff --git a/package-lock.json b/package-lock.json
index b6f339483..f0650c88e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3057,6 +3057,32 @@
         "eslint": ">=9.0.0"
       }
     },
+    "node_modules/@stylistic/eslint-plugin-jsx": {
+      "version": "4.2.0",
+      "license": "MIT",
+      "dependencies": {
+        "eslint-visitor-keys": "^4.2.0",
+        "espree": "^10.3.0",
+        "estraverse": "^5.3.0",
+        "picomatch": "^4.0.2"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      },
+      "peerDependencies": {
+        "eslint": ">=9.0.0"
+      }
+    },
+    "node_modules/@stylistic/eslint-plugin-jsx/node_modules/picomatch": {
+      "version": "4.0.2",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
     "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": {
       "version": "4.0.2",
       "license": "MIT",
@@ -4083,6 +4109,24 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/array-includes": {
+      "version": "3.1.8",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind": "^1.0.7",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.2",
+        "es-object-atoms": "^1.0.0",
+        "get-intrinsic": "^1.2.4",
+        "is-string": "^1.0.7"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/array-union": {
       "version": "1.0.2",
       "license": "MIT",
@@ -4100,6 +4144,22 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/array.prototype.flat": {
+      "version": "1.3.3",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind": "^1.0.8",
+        "define-properties": "^1.2.1",
+        "es-abstract": "^1.23.5",
+        "es-shim-unscopables": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/arraybuffer.prototype.slice": {
       "version": "1.0.4",
       "license": "MIT",
@@ -4585,6 +4645,23 @@
       "version": "1.1.2",
       "license": "MIT"
     },
+    "node_modules/builtin-modules": {
+      "version": "3.3.0",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
     "node_modules/cacheable": {
       "version": "1.8.8",
       "license": "MIT",
@@ -6798,6 +6875,16 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/es-shim-unscopables": {
+      "version": "1.1.0",
+      "license": "MIT",
+      "dependencies": {
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/es-to-primitive": {
       "version": "1.3.0",
       "license": "MIT",
@@ -7300,15 +7387,46 @@
       }
     },
     "node_modules/eslint-plugin-sonarjs": {
-      "version": "1.0.4",
-      "license": "LGPL-3.0-only",
-      "engines": {
-        "node": ">=16"
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.2.tgz",
+      "integrity": "sha512-LxjbfwI7ypENeTmGyKmDyNux3COSkMi7H/6Cal5StSLQ6edf0naP45SZR43OclaNR7WfhVTZdhOn63q3/Y6puQ==",
+      "dependencies": {
+        "@eslint-community/regexpp": "4.12.1",
+        "builtin-modules": "3.3.0",
+        "bytes": "3.1.2",
+        "functional-red-black-tree": "1.0.1",
+        "jsx-ast-utils": "3.3.5",
+        "minimatch": "9.0.5",
+        "scslre": "0.3.0",
+        "semver": "7.7.1",
+        "typescript": "^5"
       },
       "peerDependencies": {
         "eslint": "^8.0.0 || ^9.0.0"
       }
     },
+    "node_modules/eslint-plugin-sonarjs/node_modules/brace-expansion": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+      "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+      "dependencies": {
+        "balanced-match": "^1.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-sonarjs/node_modules/minimatch": {
+      "version": "9.0.5",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+      "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+      "dependencies": {
+        "brace-expansion": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/eslint-plugin-tsdoc": {
       "version": "0.4.0",
       "license": "MIT",
@@ -8271,6 +8389,10 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/functional-red-black-tree": {
+      "version": "1.0.1",
+      "license": "MIT"
+    },
     "node_modules/functions-have-names": {
       "version": "1.2.3",
       "license": "MIT",
@@ -11340,6 +11462,19 @@
         "node": "*"
       }
     },
+    "node_modules/jsx-ast-utils": {
+      "version": "3.3.5",
+      "license": "MIT",
+      "dependencies": {
+        "array-includes": "^3.1.6",
+        "array.prototype.flat": "^1.3.1",
+        "object.assign": "^4.1.4",
+        "object.values": "^1.1.6"
+      },
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
     "node_modules/jsx-dom": {
       "version": "6.4.23",
       "license": "BSD-3-Clause",
@@ -15491,6 +15626,22 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/object.values": {
+      "version": "1.2.1",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind": "^1.0.8",
+        "call-bound": "^1.0.3",
+        "define-properties": "^1.2.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/on-finished": {
       "version": "2.4.1",
       "license": "MIT",
@@ -16659,6 +16810,16 @@
         "node": ">= 10.13.0"
       }
     },
+    "node_modules/refa": {
+      "version": "0.12.1",
+      "license": "MIT",
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.8.0"
+      },
+      "engines": {
+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+      }
+    },
     "node_modules/reflect.getprototypeof": {
       "version": "1.0.10",
       "license": "MIT",
@@ -16679,6 +16840,17 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/regexp-ast-analysis": {
+      "version": "0.7.1",
+      "license": "MIT",
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.8.0",
+        "refa": "^0.12.1"
+      },
+      "engines": {
+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+      }
+    },
     "node_modules/regexp.prototype.flags": {
       "version": "1.5.4",
       "license": "MIT",
@@ -17099,6 +17271,18 @@
         "url": "https://opencollective.com/webpack"
       }
     },
+    "node_modules/scslre": {
+      "version": "0.3.0",
+      "license": "MIT",
+      "dependencies": {
+        "@eslint-community/regexpp": "^4.8.0",
+        "refa": "^0.12.0",
+        "regexp-ast-analysis": "^0.7.0"
+      },
+      "engines": {
+        "node": "^14.0.0 || >=16.0.0"
+      }
+    },
     "node_modules/section-matter": {
       "version": "1.0.0",
       "license": "MIT",
@@ -20020,9 +20204,10 @@
       "dependencies": {
         "@eslint/js": "^9.21.0",
         "@stylistic/eslint-plugin": "^4.2.0",
+        "@stylistic/eslint-plugin-jsx": "^4.2.0",
         "eslint-plugin-editorconfig": "^4.0.3",
         "eslint-plugin-import-x": "^4.6.1",
-        "eslint-plugin-sonarjs": "^1.0.4",
+        "eslint-plugin-sonarjs": "^3.0.2",
         "eslint-plugin-tsdoc": "^0.4.0",
         "globals": "^16.0.0",
         "typescript-eslint": "^8.26.0"
@@ -20100,8 +20285,7 @@
     },
     "site/node_modules/image-size": {
       "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.0.tgz",
-      "integrity": "sha512-HP07n1SpdIXGUL4VotUIOQz66MQOq8g7VN+Yj02YTVowqZScQ5i/JYU0+lkNr2pwt5j4hOpk94/UBV1ZCbS2fA==",
+      "license": "MIT",
       "bin": {
         "image-size": "bin/image-size.js"
       },
diff --git a/packages/eslint-config/README.md b/packages/eslint-config/README.md
index bf53437ac..1224dea40 100644
--- a/packages/eslint-config/README.md
+++ b/packages/eslint-config/README.md
@@ -24,36 +24,90 @@ Ensure that you have the ESLint package of version 9.0.0 or higher, shared confi
 Once installed, the configuration needs to be added in eslint configuration file:
 
 ```js
-module.exports = [
-  // ESLint configuration ...
-
-  // Apply ESL Shared ESLint Configuration
-  ...require('packages/eslint-config/index').typescript,
-  ...require('packages/eslint-config/index').recommended,
+// Import only configs you need
+import {lang, base, codestyle, medium, strict} from '@exadel/eslint-config-esl';
+
+export default [
+    // Default lang (parsers) configs, if required
+    //...lang.js,
+    //...lang.ts,
+        
+    // ESL ESLint configuration of your choice
+    ...strict // or medium, or base, or codestyle   
+    // Note: recommended sets of eslint / typscript-esint are included in base, medium and strict config.     
+        
+    // ESLint user configuration ...
 ];
 ```
 
+Please note '@exadel/eslint-config-esl' is an ESM-only package.
+You either need to use ESM in your project or run eslint independently with `.mjs` configuration file.
+
 <a name="configuration"></a>
 
 ### Configuration
 
-There are no additional configuration options for ESL Shared ESLint Configuration at that moment. 
-Please be aware that we are in the process of standardizing rule list and are currently considering supporting EcmaScript in addition to TypeScript.
+ESL Shared ESLint Configuration is split into several configurations/bunches:
+- `base` - basic light linting configuration, uses `@eslint/js/` recommended and `typescript-eslint` recommended ruleset as a base. 
+- `codestyle` - code style configuration, additionally uses `@stylistic` and `import` plugins. Does not include `base` rules.
+- `medium` - medium configuration, combination `base` and `codestyle` rules.
+- `strict` - strict configuration, includes `base` and `codestyle`, extended with `sonarjs` and `tsdoc` plugins and the most restrictive settings.
+
+The `base` ruleset recommended for consumers to have basic code checks similar to ESL source code.
+The `codestyle` ruleset is recommended for projects that want to follow ESL code style.
+The `medium` ruleset is a shortcut for projects that want to have both basic and code style checks.
+The `strict` ruleset is recommended for projects that completely follow ESL code style and want to have the most restrictive checks.
+
+### Default lang (parsers) configs
 
-Still you are able to override any rule from the configuration in your project's ESLint configuration file 
-by declaring it in the `rules` section of the configuration file after applying the shared configuration.
+ESL Shared ESLint Configuration includes default lang (parsers) configs for JavaScript and TypeScript.
+
+Feel free to extend or default them manually.
+
+Default JavaScript parser config:
+```js
+const js = {  // lang.js
+    files: ['**/*.js', '**/*.mjs'],
+    languageOptions: {
+        ecmaVersion: 2017,
+        sourceType: 'module',
+        globals: {
+            ...globals.browser,
+            ...globals.node
+        }
+    }
+};
+```
+
+Default TypeScript parser config:
+```js
+const ts = { // lang.ts
+    files: ['**/*.ts', '**/*.tsx'],
+    languageOptions: {
+        parser: tsParser, // @typescript-eslint/parser
+        ecmaVersion: 2017,
+        sourceType: 'module',
+        parserOptions: {
+            projectService: true
+        },
+        globals: {
+            ...globals.browser
+        }
+    }
+};
+```
 
 ### Inner Plugins
 
 ESL Shared ESLint Configuration includes several inner plugins that are used to provide additional rules and configurations for ESLint.
 Here is the list of included plugins and their ESLint aliases:
 
-- `@stylistic` - code style rules from [@stylistic](https://eslint.style/) project.
+- `@eslint/js` - basic ESLint rules from [eslint](https://eslint.org/) project.
 - `typescript-eslint` - TypeScript specific rules from [TypeScript ESLint](https://typescript-eslint.io/) project.
+- `@stylistic` - code style rules from [@stylistic](https://eslint.style/) project.
 - `import` - rules for imports from [eslint-plugin-import-x](https://www.npmjs.com/package/eslint-plugin-import-x);
 - `tsdoc` - rules for TSDoc comments from [eslint-plugin-tsdoc](https://www.npmjs.com/package/eslint-plugin-tsdoc);
-- `sonarjs` - rules for code quality from [eslint-plugin-sonarjs](https://www.npmjs.com/package/eslint-plugin-sonarjs); 
-  Note: uses 1.*.* version of the plugin due to incompatibility with flat config.
+- `sonarjs` - rules for code quality from [eslint-plugin-sonarjs](https://www.npmjs.com/package/eslint-plugin-sonarjs);
 - `editorconfig` - rules for EditorConfig from [eslint-plugin-editorconfig](https://www.npmjs.com/package/eslint-plugin-editorconfig);
 
-All mentioned plugins could be used from shared configuration without additional installation.
+All mentioned plugins are direct dependencies of `@exadel/eslint-config-esl` package, you don't need to install them separately.
diff --git a/packages/eslint-config/index.js b/packages/eslint-config/index.js
index 74a4ff01a..5e6451808 100644
--- a/packages/eslint-config/index.js
+++ b/packages/eslint-config/index.js
@@ -1,27 +1,17 @@
-import eslintjs from '@eslint/js';
-import {configs as typescriptConfigs} from 'typescript-eslint';
+import lang from './rules/lang/index.js';
+import base from './rules/base/index.js';
+import extended from './rules/strict/index.js';
+import codestyle from './rules/codestyle/index.js';
 
-import esbase from './rules/es.base.js';
-import tsbase from './rules/ts.base.js';
-import stylistic from './rules/stylistic.js';
+export {lang, base, codestyle};
 
-import tsdoc from './rules/tsdoc.js';
-import sonarjs from './rules/sonarjs.js';
-import editorconfig from './rules/editorconfig.js';
-import importconfig from './rules/import.js';
-
-import lang from './rules/lang.ts.js';
-
-export {lang};
+export const medium = [
+  ...base,
+  ...codestyle
+];
 
 export const strict = [
-  eslintjs.configs.recommended,
-  ...typescriptConfigs.recommended,
-  ...esbase,
-  ...tsbase,
-  ...stylistic,
-  ...sonarjs,
-  ...editorconfig,
-  ...importconfig,
-  ...tsdoc
+  ...base,
+  ...extended,
+  ...codestyle
 ];
diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json
index 8ffa3ace3..697a7d2a2 100644
--- a/packages/eslint-config/package.json
+++ b/packages/eslint-config/package.json
@@ -15,16 +15,18 @@
   "license": "MIT",
   "description": "Shared ESLint config used by ESL (@exadel/esl) team. Internal projects usage.",
   "main": "index.js",
+  "sideEffects": false,
   "scripts": {
     "test": ""
   },
   "dependencies": {
     "@eslint/js": "^9.21.0",
     "@stylistic/eslint-plugin": "^4.2.0",
+    "@stylistic/eslint-plugin-jsx": "^4.2.0",
     "globals": "^16.0.0",
     "eslint-plugin-editorconfig": "^4.0.3",
     "eslint-plugin-import-x": "^4.6.1",
-    "eslint-plugin-sonarjs": "^1.0.4",
+    "eslint-plugin-sonarjs": "^3.0.2",
     "eslint-plugin-tsdoc": "^0.4.0",
     "typescript-eslint": "^8.26.0"
   },
diff --git a/packages/eslint-config/rules/base/ecmascript.js b/packages/eslint-config/rules/base/ecmascript.js
new file mode 100644
index 000000000..f7bb4feb0
--- /dev/null
+++ b/packages/eslint-config/rules/base/ecmascript.js
@@ -0,0 +1,139 @@
+export default [
+  {
+    rules: {
+      // Require return statement in array modification method's callback
+      'array-callback-return': 'error',
+
+      // Limit Cyclomatic Complexity
+      'complexity': ['warn', {max: 20}],
+
+      // Require usage of === and !==
+      'eqeqeq': 'error',
+
+      // Require Guarding for-in to exclude prototype keys
+      'guard-for-in': 'warn',
+
+      // There is no blacklisted identifiers
+      'id-blacklist': 'off',
+
+      // Max 4 parameters per function
+      'max-params': ['warn', 4],
+
+      // Max 3 classes per file (ideally 1 per file)
+      'max-classes-per-file': ['warn', 3],
+
+      // Enforce a maximum depth that blocks can be nested to 4
+      'max-depth': 'error',
+
+      // Limit max lines count per file to 500
+      'max-lines': ['warn', {
+        max: 350,
+        skipBlankLines: true,
+        skipComments: true
+      }],
+
+      // Enforce a maximum depth that callbacks can be nested
+      'max-nested-callbacks': ['error', {'max': 3}],
+
+      // Bitwise operators are not recommended
+      'no-bitwise': 'warn',
+
+      // Disallow use of caller/callee
+      'no-caller': 'error',
+
+      // Console API is formally allowed
+      'no-console': 'off',
+
+      // Disallow useless else statements
+      'no-else-return': 'error',
+
+      // Disallow null comparisons without type-checking operators
+      'no-eq-null': 'error',
+
+      // Warn if unsafe eval used
+      'no-eval': 'warn',
+
+      // Disallow extending native types
+      'no-extend-native': 'error',
+
+      // Disallow unnecessary calls to .bind()
+      'no-extra-bind': 'error',
+
+      // Disallow the use of eval()-like methods
+      'no-implied-eval': 'error',
+
+      // Labels are not allowed
+      'no-labels': 'error',
+
+      // Disallow unnecessary nested blocks
+      'no-lone-blocks': 'error',
+
+      // Disallow function declarations that contain unsafe references inside loop statements
+      'no-loop-func': 'error',
+
+      // Disallow new operators with the String, Number, and Boolean objects
+      'no-new-wrappers': 'error',
+
+      // Disallow the use of the __proto__ property
+      'no-proto': 'error',
+
+      // Warn about comma operators
+      'no-sequences': 'warn',
+
+      // Warn about comparisons where both sides are exactly the same
+      'no-self-compare': 'warn',
+
+      // Disallow unmodified loop conditions
+      'no-unmodified-loop-condition': 'error',
+
+      // Disallow loops with a body that allows only one iteration
+      'no-unreachable-loop': 'error',
+
+      // Disallow initializing variables to undefined
+      'no-undef-init': 'error',
+
+      // Underscores allowed due to 'private-member' notation
+      'no-underscore-dangle': 'off',
+
+      // Disallow unnecessary concatenation of literals or template literals
+      'no-useless-concat': 'error',
+
+      // Disallow renaming import, export, and destructured assignments to the same name
+      'no-useless-rename': 'error',
+
+      // Deprecate return statement without need
+      'no-useless-return': 'warn',
+
+      // 'var' declaration operator is not allowed
+      'no-var': 'error',
+
+      // Warn to prefer shorthand object keys
+      'object-shorthand': 'warn',
+
+      // Prefer const declaration operator
+      'prefer-const': 'warn',
+
+      // No spread preferences in bounds of the library
+      'prefer-spread': 'off',
+
+      // Require rest parameters instead of arguments
+      'prefer-rest-params': 'warn',
+
+      // Warn about assignments that can lead to race conditions due to usage of await or yield
+      'require-atomic-updates': 'warn'
+    }
+  },
+  {
+    files: ['**/*.test.ts', '**/*.spec.ts', '**/*.test.js', '**/*.spec.js'],
+    rules: {
+      // Max parameters limit is not applied to tests
+      'max-params': 'off',
+      // No class count limit for tests
+      'max-classes-per-file': 'off',
+      // Self compare might be singleton test case
+      'no-self-compare': 'off',
+      // Jest tests use callback nesting by design
+      'max-nested-callbacks': 'off'
+    }
+  }
+];
diff --git a/packages/eslint-config/rules/import.js b/packages/eslint-config/rules/base/imports.js
similarity index 61%
rename from packages/eslint-config/rules/import.js
rename to packages/eslint-config/rules/base/imports.js
index 71a013581..7c0aae42e 100644
--- a/packages/eslint-config/rules/import.js
+++ b/packages/eslint-config/rules/base/imports.js
@@ -6,13 +6,6 @@ export default [
       'import': importPlugin
     },
     rules: {
-      // Enforce a convention in module import order
-      'import/order': [
-        'warn', {
-          'groups': ['builtin', 'external', 'parent', 'sibling', 'index', 'object', 'type']
-        }
-      ],
-
       // Reports funny business with exports, like repeated exports of names or defaults.
       'import/export': 'warn',
 
@@ -20,9 +13,6 @@ export default [
       'no-duplicate-imports': 'off',
       'import/no-duplicates': 'warn',
 
-      // Verifies that all named imports are part of the set of named exports in the referenced module.
-      'import/named': 'error',
-
       // Forbid a module from importing itself
       'import/no-self-import': 'error',
 
diff --git a/packages/eslint-config/rules/base/index.js b/packages/eslint-config/rules/base/index.js
new file mode 100644
index 000000000..983130be5
--- /dev/null
+++ b/packages/eslint-config/rules/base/index.js
@@ -0,0 +1,16 @@
+import eslintjs from '@eslint/js';
+import {configs as typescriptConfigs} from 'typescript-eslint';
+
+import esbase from './ecmascript.js';
+import tsbase from './typescript.js';
+import imports from './imports.js';
+
+export {esbase, tsbase, imports};
+
+export default [
+  eslintjs.configs.recommended,
+  ...typescriptConfigs.recommended,
+  ...esbase,
+  ...tsbase,
+  ...imports
+];
diff --git a/packages/eslint-config/rules/ts.base.js b/packages/eslint-config/rules/base/typescript.js
similarity index 84%
rename from packages/eslint-config/rules/ts.base.js
rename to packages/eslint-config/rules/base/typescript.js
index 8075f57a2..ed114a4fd 100644
--- a/packages/eslint-config/rules/ts.base.js
+++ b/packages/eslint-config/rules/base/typescript.js
@@ -2,29 +2,38 @@ import {plugin} from 'typescript-eslint';
 
 export default [
   {
-    files: ['**/*.ts', '**/*.tsx'],
     plugins: {
       '@typescript-eslint': plugin
     },
     rules: {
-      // Enforces consistent usage of type imports
-      '@typescript-eslint/consistent-type-imports': [
-        'error', {
-          prefer: 'type-imports'
-        }
-      ],
-
-      // Disallow the use of custom TypeScript modules and namespaces
-      '@typescript-eslint/no-namespace': 'error',
+      // Enforce default parameters to be last
+      '@typescript-eslint/default-param-last': 'error',
 
-      // Flags unnecessary equality comparisons against boolean literals
-      '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
+      // Warn about invalid definition of new and constructor
+      '@typescript-eslint/no-misused-new': 'warn',
 
-      // Disallows the use of require statements except in import statements
-      '@typescript-eslint/no-var-requires': 'error',
+      // Allows unused expressions
+      '@typescript-eslint/no-unused-expressions': 'off',
 
-      // Enforce default parameters to be last
-      '@typescript-eslint/default-param-last': 'error',
+      // Warn about unused variables
+      '@typescript-eslint/no-unused-vars': [
+        'warn',
+        {
+          args: 'none',
+          caughtErrors: 'none',
+          vars: 'all'
+        }
+      ]
+    }
+  },
+  {
+    files: ['**/*.ts', '**/*.tsx'],
+    plugins: {
+      '@typescript-eslint': plugin
+    },
+    rules: {
+      // Warn about awaiting of a value that is not a Thenable
+      '@typescript-eslint/await-thenable': 'warn',
 
       // Prefers member overloads to be consecutive
       '@typescript-eslint/adjacent-overload-signatures': 'warn',
@@ -32,11 +41,8 @@ export default [
       // Prefers using either T[] or Array<T> for arrays
       '@typescript-eslint/array-type': 'warn',
 
-      // Warn about awaiting of a value that is not a Thenable
-      '@typescript-eslint/await-thenable': 'warn',
-
-      // Bans @ts-<directive> comments from being used or requires descriptions after directive
-      '@typescript-eslint/ban-ts-comment': 'warn',
+      // Does not ban @ts-<directive> comments in base ESLint configuration
+      '@typescript-eslint/ban-ts-comment': 'off',
 
       // Bans Function types from being used
       '@typescript-eslint/no-unsafe-function-type': 'warn',
@@ -48,8 +54,8 @@ export default [
         }
       ],
 
-      // Warn about invalid definition of new and constructor
-      '@typescript-eslint/no-misused-new': 'warn',
+      // Allow the use of custom TypeScript modules and namespaces (in base configuration)
+      '@typescript-eslint/no-namespace': 'off',
 
       // Warns when a namespace qualifier is unnecessary
       '@typescript-eslint/no-unnecessary-qualifier': 'warn',
@@ -57,32 +63,17 @@ export default [
       // Prefers function types instead of interfaces with call signatures
       '@typescript-eslint/prefer-function-type': 'warn',
 
-      // Warns if a type assertion does not change the type of an expression
-      '@typescript-eslint/no-unnecessary-type-assertion': 'warn',
-
-      // It's recommended to pass a compareFn to the Array.sort method
-      '@typescript-eslint/require-array-sort-compare': 'warn',
-
-      // Warns if missed return type on functions or class methods
-      '@typescript-eslint/explicit-function-return-type': 'warn',
-
       /** Ignores if two overloads could be unified into one by using a union or an optional/rest parameter
        * Because of the possibility to separate TSDocs between different function/method overloads
        */
       '@typescript-eslint/unified-signatures': 'off',
 
-      // Allows the delete operator with computed key expressions
-      '@typescript-eslint/no-dynamic-delete': 'off',
-
       // Allows the use of parameter properties in class constructors
       '@typescript-eslint/no-parameter-properties': 'off',
 
       // Allows the use of type aliases
       '@typescript-eslint/no-type-alias': 'off',
 
-      // There is no prefer of ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated
-      '@typescript-eslint/prefer-for-of': 'off',
-
       // There is no restriction when adding two variables, operands must both be of type number or of type string
       '@typescript-eslint/restrict-plus-operands': 'off',
 
@@ -121,6 +112,9 @@ export default [
       // There is no preferences of a particular method signature syntax
       '@typescript-eslint/method-signature-style': 'off',
 
+      // It's recommended to pass a compareFn to the Array.sort method
+      '@typescript-eslint/require-array-sort-compare': 'warn',
+
       // Warn about non-null assertion in locations that may be confusing
       '@typescript-eslint/no-confusing-non-null-assertion': 'warn',
 
@@ -147,9 +141,6 @@ export default [
       // There is no requirement of using promises in places not designed to handle them
       '@typescript-eslint/no-misused-promises': 'off',
 
-      // Check that type arguments will not be used if not required
-      '@typescript-eslint/no-unnecessary-type-arguments': 'warn',
-
       // Warn about unnecessary constraints on generic types
       '@typescript-eslint/no-unnecessary-type-constraint': 'warn',
 
@@ -228,17 +219,6 @@ export default [
       // Warn about throwing literals as exceptions
       '@typescript-eslint/only-throw-error': 'warn',
 
-      // Allows unused expressions
-      '@typescript-eslint/no-unused-expressions': 'off',
-
-      // Warn about unused variables
-      '@typescript-eslint/no-unused-vars': [
-        'warn',
-        {
-          vars: 'all', caughtErrors: 'none', args: 'none'
-        }
-      ],
-
       // Warn about unnecessary constructors
       '@typescript-eslint/no-useless-constructor': 'warn',
 
@@ -249,17 +229,6 @@ export default [
       '@typescript-eslint/return-await': 'warn'
     }
   },
-  {
-    files: ['**/*.shape.ts'],
-    plugins: {
-      '@typescript-eslint': plugin
-    },
-    rules: {
-      '@typescript-eslint/no-namespace': 'off',
-      // Temporary of as false positive
-      '@typescript-eslint/no-unnecessary-type-arguments': 'off'
-    }
-  },
   {
     files: ['**/*.test.ts', '**/*.spec.ts'],
     plugins: {
diff --git a/packages/eslint-config/rules/codestyle/ecmascript.js b/packages/eslint-config/rules/codestyle/ecmascript.js
new file mode 100644
index 000000000..ea255c5e9
--- /dev/null
+++ b/packages/eslint-config/rules/codestyle/ecmascript.js
@@ -0,0 +1,8 @@
+export default [
+  {
+    rules: {
+      // Require following curly brace conventions
+      'curly': ['warn', 'multi-line'],
+    }
+  }
+];
diff --git a/packages/eslint-config/rules/editorconfig.js b/packages/eslint-config/rules/codestyle/editorconfig.js
similarity index 100%
rename from packages/eslint-config/rules/editorconfig.js
rename to packages/eslint-config/rules/codestyle/editorconfig.js
diff --git a/packages/eslint-config/rules/codestyle/imports.js b/packages/eslint-config/rules/codestyle/imports.js
new file mode 100644
index 000000000..b43e0b543
--- /dev/null
+++ b/packages/eslint-config/rules/codestyle/imports.js
@@ -0,0 +1,17 @@
+import importPlugin from 'eslint-plugin-import-x';
+
+export default [
+  {
+    plugins: {
+      'import': importPlugin
+    },
+    rules: {
+      // Enforce a convention in module import order
+      'import/order': [
+        'warn', {
+          'groups': ['builtin', 'external', 'parent', 'sibling', 'index', 'object', 'type']
+        }
+      ]
+    }
+  }
+];
diff --git a/packages/eslint-config/rules/codestyle/index.js b/packages/eslint-config/rules/codestyle/index.js
new file mode 100644
index 000000000..77d1e7bad
--- /dev/null
+++ b/packages/eslint-config/rules/codestyle/index.js
@@ -0,0 +1,13 @@
+import ecmascript from './ecmascript.js';
+import typescript from './typescript.js';
+import stylistic from './stylistic.js';
+import editorconfig from './editorconfig.js';
+import imports from './imports.js';
+
+export default [
+  ...ecmascript,
+  ...typescript,
+  ...stylistic,
+  ...editorconfig,
+  ...imports
+];
diff --git a/packages/eslint-config/rules/stylistic.js b/packages/eslint-config/rules/codestyle/stylistic.js
similarity index 90%
rename from packages/eslint-config/rules/stylistic.js
rename to packages/eslint-config/rules/codestyle/stylistic.js
index a229a3629..b4b41cce9 100644
--- a/packages/eslint-config/rules/stylistic.js
+++ b/packages/eslint-config/rules/codestyle/stylistic.js
@@ -1,4 +1,5 @@
 import stylistic from '@stylistic/eslint-plugin';
+import stylisticJsx from '@stylistic/eslint-plugin-jsx';
 
 export default [
   {
@@ -64,6 +65,9 @@ export default [
         }
       ],
 
+      // Enforce a maximum number of statements allowed per line
+      '@stylistic/max-statements-per-line': ['error', {max: 2}],
+
       // Require parentheses when invoking a constructor with no arguments
       '@stylistic/new-parens': 'error',
 
@@ -132,4 +136,14 @@ export default [
       '@stylistic/brace-style': 'off',
     }
   },
+  {
+    files: ['**/*.tsx', '**/*.jsx'],
+    plugins: {
+      '@stylistic/jsx': stylisticJsx
+    },
+    rules: {
+      '@stylistic/indent': 'off',
+      '@stylistic/jsx/jsx-indent-props': ['warn', 'first']
+    }
+  }
 ];
diff --git a/packages/eslint-config/rules/codestyle/typescript.js b/packages/eslint-config/rules/codestyle/typescript.js
new file mode 100644
index 000000000..3472ee700
--- /dev/null
+++ b/packages/eslint-config/rules/codestyle/typescript.js
@@ -0,0 +1,11 @@
+export default [
+  {
+    rules: {
+      '@typescript-eslint/naming-convention': ['warn', {
+        'selector': 'variable',
+        'format': ['camelCase', 'PascalCase', 'UPPER_CASE'],
+        'leadingUnderscore': 'allow'
+      }]
+    }
+  }
+];
diff --git a/packages/eslint-config/rules/es.base.js b/packages/eslint-config/rules/es.base.js
deleted file mode 100644
index ec309d307..000000000
--- a/packages/eslint-config/rules/es.base.js
+++ /dev/null
@@ -1,63 +0,0 @@
-export default [
-  {
-    rules: {
-      // Require Guarding for-in to exclude prototype keys
-      'guard-for-in': 'warn',
-
-      // There is no blacklisted identifiers
-      'id-blacklist': 'off',
-
-      // Bitwise operators are not recommended
-      'no-bitwise': 'warn',
-
-      // Console API is formally allowed
-      'no-console': 'off',
-
-      // Labels are not allowed
-      'no-labels': 'error',
-
-      // Require implicit radix parameter for parseInt
-      'radix': 'error',
-
-      // Limit Cyclomatic Complexity
-      'complexity': 'warn',
-
-      // Require following curly brace conventions
-      'curly': ['warn', 'multi-line'],
-
-      // Max 3 classes per file (ideally 1 per file)
-      'max-classes-per-file': ['warn', 3],
-
-      // Underscores allowed due to 'private-member' notation
-      'no-underscore-dangle': 'off',
-
-      // 'var' declaration operator is not allowed
-      'no-var': 'error',
-
-      // Warn to prefer shorthand object keys
-      'object-shorthand': 'warn',
-
-      // Disallow multiple declaration per one line or declaration operator
-      'one-var': ['error', 'never'],
-
-      // Using of arrow functions is optional
-      'prefer-arrow-callback': 'off',
-
-      // Prefer const declaration operator
-      'prefer-const': 'error',
-
-      // Limit max lines count per file to 500
-      'max-lines': ['warn', 500],
-
-      // No spread preferences in bounds of the library
-      'prefer-spread': 'off'
-    }
-  },
-  {
-    files: ['**/*.test.ts', '**/*.spec.ts'],
-    rules: {
-      // no class count limit for tests
-      'max-classes-per-file': 'off'
-    }
-  }
-];
diff --git a/packages/eslint-config/rules/lang/ecmascript.js b/packages/eslint-config/rules/lang/ecmascript.js
new file mode 100644
index 000000000..5d42ed131
--- /dev/null
+++ b/packages/eslint-config/rules/lang/ecmascript.js
@@ -0,0 +1,13 @@
+import globals from 'globals';
+
+export default [{
+  files: ['**/*.js', '**/*.mjs'],
+  languageOptions: {
+    ecmaVersion: 2017,
+    sourceType: 'module',
+    globals: {
+      ...globals.browser,
+      ...globals.node
+    }
+  }
+}];
diff --git a/packages/eslint-config/rules/lang/index.js b/packages/eslint-config/rules/lang/index.js
new file mode 100644
index 000000000..a0dc3e543
--- /dev/null
+++ b/packages/eslint-config/rules/lang/index.js
@@ -0,0 +1,7 @@
+import langJS from './ecmascript.js';
+import langTS from './typescript.js';
+
+export default {
+  js: langJS,
+  ts: langTS
+};
diff --git a/packages/eslint-config/rules/lang.ts.js b/packages/eslint-config/rules/lang/typescript.js
similarity index 100%
rename from packages/eslint-config/rules/lang.ts.js
rename to packages/eslint-config/rules/lang/typescript.js
diff --git a/packages/eslint-config/rules/strict/ecmascript.js b/packages/eslint-config/rules/strict/ecmascript.js
new file mode 100644
index 000000000..d3335e6c8
--- /dev/null
+++ b/packages/eslint-config/rules/strict/ecmascript.js
@@ -0,0 +1,42 @@
+export default [
+  {
+    rules: {
+      // Limit Cyclomatic Complexity
+      'complexity': ['warn', {max: 16}],
+
+      // Require Guarding for-in to exclude prototype keys
+      'guard-for-in': 'error',
+
+      // Require function to be compact
+      'max-lines-per-function': ['error', {max: 55}],
+
+      // Disallow redundant boolean cast
+      'no-extra-boolean-cast': 'warn',
+
+      // Deprecate a catch without payload
+      'no-useless-catch': 'error',
+
+      // Deprecate return statement without need
+      'no-useless-return': 'error',
+
+      // Disallow multiple declaration per one line or declaration operator
+      'one-var': ['error', 'never'],
+
+      // Prefer const declaration operator
+      'prefer-const': 'error',
+
+      // Require implicit radix parameter for parseInt
+      'radix': 'error'
+    }
+  },
+  {
+    files: ['**/*.test.ts', '**/*.test.js', '**/*.spec.ts', '**/*.spec.js'],
+    rules: {
+      // A loyal limit for cyclomatic complexity
+      'complexity': ['warn', {max: 25}],
+
+      // No limitation in tests
+      'max-lines-per-function': 'off',
+    }
+  }
+];
diff --git a/packages/eslint-config/rules/strict/imports.js b/packages/eslint-config/rules/strict/imports.js
new file mode 100644
index 000000000..c1104f37b
--- /dev/null
+++ b/packages/eslint-config/rules/strict/imports.js
@@ -0,0 +1,13 @@
+import importPlugin from 'eslint-plugin-import-x';
+
+export default [
+  {
+    plugins: {
+      'import': importPlugin
+    },
+    rules: {
+      // Verifies that all named imports are part of the set of named exports in the referenced module.
+      'import/named': 'error'
+    }
+  }
+];
diff --git a/packages/eslint-config/rules/strict/index.js b/packages/eslint-config/rules/strict/index.js
new file mode 100644
index 000000000..89450b589
--- /dev/null
+++ b/packages/eslint-config/rules/strict/index.js
@@ -0,0 +1,13 @@
+import ecmascript from './ecmascript.js';
+import typescript from './typescript.js';
+import imports from './imports.js';
+import sonarjs from './sonarjs.js';
+import tsdoc from './tsdoc.js';
+
+export default [
+  ...ecmascript,
+  ...typescript,
+  ...imports,
+  ...sonarjs,
+  ...tsdoc
+];
diff --git a/packages/eslint-config/rules/sonarjs.js b/packages/eslint-config/rules/strict/sonarjs.js
similarity index 80%
rename from packages/eslint-config/rules/sonarjs.js
rename to packages/eslint-config/rules/strict/sonarjs.js
index cad0bd931..69f3e4b54 100644
--- a/packages/eslint-config/rules/sonarjs.js
+++ b/packages/eslint-config/rules/strict/sonarjs.js
@@ -26,20 +26,12 @@ export default [
       // Disallows identical functions
       'sonarjs/no-identical-functions': 'warn',
 
-      /** It is needlessly complex to invert the result of a boolean comparison.
-       *  The opposite comparison should be made instead.
-       */
-      'sonarjs/no-inverted-boolean-check': 'warn',
-
       // Disallows nested switch structures as they are difficult to understand
       'sonarjs/no-nested-switch': 'error',
 
       // Redundant Boolean literals should be removed from expressions to improve readability.
       'sonarjs/no-redundant-boolean': 'error',
 
-      // Disallows redundant jump statements, such as return, break and continue
-      'sonarjs/no-redundant-jump': 'error',
-
       // Code is clearest when each statement has its own line.
       'sonarjs/no-same-line-conditional': 'error',
 
@@ -49,10 +41,8 @@ export default [
       // This rule raises an issue when no methods are called on a collection other than those that add or remove values.
       'sonarjs/no-unused-collection': 'error',
 
-      // Disallows empty catch statements
-      'sonarjs/no-useless-catch': 'error',
-
-      /** When only the condition expression is defined in a for loop, and the initialization and
+      /**
+       * When only the condition expression is defined in a for loop, and the initialization and
        * increment expressions are missing, a while loop should be used instead to increase readability.
        */
       'sonarjs/prefer-while': 'error',
diff --git a/packages/eslint-config/rules/tsdoc.js b/packages/eslint-config/rules/strict/tsdoc.js
similarity index 100%
rename from packages/eslint-config/rules/tsdoc.js
rename to packages/eslint-config/rules/strict/tsdoc.js
diff --git a/packages/eslint-config/rules/strict/typescript.js b/packages/eslint-config/rules/strict/typescript.js
new file mode 100644
index 000000000..51e57ed54
--- /dev/null
+++ b/packages/eslint-config/rules/strict/typescript.js
@@ -0,0 +1,51 @@
+import {plugin} from 'typescript-eslint';
+
+export default [
+  {
+    files: ['**/*.ts', '**/*.tsx'],
+    plugins: {
+      '@typescript-eslint': plugin
+    },
+    rules: {
+      // Bans @ts-<directive> comments from being used or requires descriptions after directive
+      '@typescript-eslint/ban-ts-comment': 'warn',
+
+      // Enforces consistent usage of type imports
+      '@typescript-eslint/consistent-type-imports': [
+        'error', {
+          prefer: 'type-imports'
+        }
+      ],
+
+      // Warns if missed return type on functions or class methods
+      '@typescript-eslint/explicit-function-return-type': ['warn'],
+
+      // Disallow the use of custom TypeScript modules and namespaces
+      '@typescript-eslint/no-namespace': 'error',
+
+      // Flags unnecessary equality comparisons against boolean literals
+      '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error',
+
+      // Check that type arguments will not be used if not required
+      '@typescript-eslint/no-unnecessary-type-arguments': 'warn',
+
+      // Warns if a type assertion does not change the type of an expression
+      '@typescript-eslint/no-unnecessary-type-assertion': 'warn'
+    }
+  },
+  {
+    files: ['**/*.shape.ts'],
+    rules: {
+      '@typescript-eslint/no-namespace': 'off',
+      // Temporary of as false positive
+      '@typescript-eslint/no-unnecessary-type-arguments': 'off'
+    }
+  },
+  {
+    files: ['**/*.test.ts', '**/*.spec.ts'],
+    rules: {
+      '@typescript-eslint/ban-ts-comment': 'off',
+      '@typescript-eslint/explicit-function-return-type': 'off'
+    }
+  }
+];
diff --git a/src/modules/esl-carousel/plugin/esl-carousel.plugin.ts b/src/modules/esl-carousel/plugin/esl-carousel.plugin.ts
index 042323e9f..930fc1238 100644
--- a/src/modules/esl-carousel/plugin/esl-carousel.plugin.ts
+++ b/src/modules/esl-carousel/plugin/esl-carousel.plugin.ts
@@ -53,12 +53,11 @@ export abstract class ESLCarouselPlugin<Config> extends ESLMixinElement {
     if (($host as unknown) instanceof ESLCarousel) {
       super.connectedCallback();
       return true;
-    } else {
-      const {is} = this.constructor as typeof ESLCarouselPlugin;
-      console.warn('[ESL]: ESLCarousel %s plugin rejected for non correct target %o', is, $host);
-      this.$host.removeAttribute(is);
-      return false;
     }
+    const {is} = this.constructor as typeof ESLCarouselPlugin;
+    console.warn('[ESL]: ESLCarousel %s plugin rejected for non correct target %o', is, $host);
+    this.$host.removeAttribute(is);
+    return false;
   }
 
   protected override attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string | null): void {
diff --git a/src/modules/esl-event-listener/core/listener.ts b/src/modules/esl-event-listener/core/listener.ts
index 7a32ae0e8..aa8c006c5 100644
--- a/src/modules/esl-event-listener/core/listener.ts
+++ b/src/modules/esl-event-listener/core/listener.ts
@@ -103,6 +103,7 @@ export class ESLEventListener implements ESLListenerDefinition, EventListenerObj
   public get handleEvent(): EventListener {
     const handlerBound = this.handler.bind(this.host);
     const handlerFull = this.once
+      // eslint-disable-next-line no-sequences
       ? (e: Event): void => (handlerBound(e), this.unsubscribe())
       : handlerBound;
     return this.selector


Test patch:
diff --git a/src/modules/esl-base-element/test/element.test.ts b/src/modules/esl-base-element/test/element.test.ts
index 3361bb2d0..123207f55 100644
--- a/src/modules/esl-base-element/test/element.test.ts
+++ b/src/modules/esl-base-element/test/element.test.ts
@@ -37,6 +37,7 @@ describe('ESLBaseElement', () => {
     expect(() => TestElement2.register('test-test')).not.toThrowError();
     expect(() => TestElement2.register('test-test-2')).toThrowError();
     try {
+      // eslint-disable-next-line require-atomic-updates
       TestElement2.is = 'test-test-2';
     } catch { /* empty */
     }
diff --git a/src/modules/esl-event-listener/test/targets/resize.test.ts b/src/modules/esl-event-listener/test/targets/resize.test.ts
index 48edb0339..bc8e2ec33 100644
--- a/src/modules/esl-event-listener/test/targets/resize.test.ts
+++ b/src/modules/esl-event-listener/test/targets/resize.test.ts
@@ -23,22 +23,22 @@ describe('ESLResizeObserverTarget EventTarget adapter', () => {
 
     test('ESLResizeObserverTarget.for(undefined) returns null without error', () => {
       expect(ESLResizeObserverTarget.for(undefined as any)).toBeNull();
-      expect(consoleSpy).toBeCalled();
+      expect(consoleSpy).toHaveBeenCalled();
     });
 
     test('ESLResizeObserverTarget.for(null) returns null without error', () => {
       expect(ESLResizeObserverTarget.for(null as any)).toBeNull();
-      expect(consoleSpy).toBeCalled();
+      expect(consoleSpy).toHaveBeenCalled();
     });
 
     test('ESLResizeObserverTarget.for(123) returns null without error', () => {
       expect(ESLResizeObserverTarget.for(123 as any)).toBeNull();
-      expect(consoleSpy).toBeCalled();
+      expect(consoleSpy).toHaveBeenCalled();
     });
 
     test('ESLResizeObserverTarget.for({}) returns null without error', () => {
       expect(ESLResizeObserverTarget.for({} as any)).toBeNull();
-      expect(consoleSpy).toBeCalled();
+      expect(consoleSpy).toHaveBeenCalled();
     });
   });
 
@@ -59,22 +59,22 @@ describe('ESLResizeObserverTarget EventTarget adapter', () => {
       test('Subscription produces ResizeObserver interaction', () => {
         target.addEventListener(cb1);
         expect(mock.observe).lastCalledWith(el);
-        expect(mock.unobserve).not.toBeCalled();
+        expect(mock.unobserve).not.toHaveBeenCalled();
       });
       test('Second subscription does not produces ResizeObserver interaction', () => {
         target.addEventListener(cb2);
-        expect(mock.observe).not.toBeCalled();
-        expect(mock.unobserve).not.toBeCalled();
+        expect(mock.observe).not.toHaveBeenCalled();
+        expect(mock.unobserve).not.toHaveBeenCalled();
       });
       test('Single unsubscription does not produces internal unsubscription', () => {
         target.removeEventListener(cb2);
-        expect(mock.observe).not.toBeCalled();
-        expect(mock.unobserve).not.toBeCalled();
+        expect(mock.observe).not.toHaveBeenCalled();
+        expect(mock.unobserve).not.toHaveBeenCalled();
       });
       test('Removal of all of subscriptions produces internal unsubscription', () => {
         target.removeEventListener(cb1);
-        expect(mock.observe).not.toBeCalled();
-        expect(mock.unobserve).toBeCalled();
+        expect(mock.observe).not.toHaveBeenCalled();
+        expect(mock.unobserve).toHaveBeenCalled();
       });
     });
 
@@ -99,22 +99,22 @@ describe('ESLResizeObserverTarget EventTarget adapter', () => {
       test('ESLDomElementRelated subscription produces ResizeObserver interaction', () => {
         targetMixin.addEventListener(cb1);
         expect(mock.observe).lastCalledWith(el);
-        expect(mock.unobserve).not.toBeCalled();
+        expect(mock.unobserve).not.toHaveBeenCalled();
       });
       test('Repeated subscription of ESLDomElementRelated host does not produces ResizeObserver interaction', () => {
         target.addEventListener(cb2);
-        expect(mock.observe).not.toBeCalled();
-        expect(mock.unobserve).not.toBeCalled();
+        expect(mock.observe).not.toHaveBeenCalled();
+        expect(mock.unobserve).not.toHaveBeenCalled();
       });
       test('Single unsubscription does not produces internal unsubscription', () => {
         targetMixin.removeEventListener(cb1);
-        expect(mock.observe).not.toBeCalled();
-        expect(mock.unobserve).not.toBeCalled();
+        expect(mock.observe).not.toHaveBeenCalled();
+        expect(mock.unobserve).not.toHaveBeenCalled();
       });
       test('Removal of all of subscriptions produces internal unsubscription', () => {
         target.removeEventListener(cb2);
-        expect(mock.observe).not.toBeCalled();
-        expect(mock.unobserve).toBeCalled();
+        expect(mock.observe).not.toHaveBeenCalled();
+        expect(mock.unobserve).toHaveBeenCalled();
       });
     });
   });
@@ -129,20 +129,20 @@ describe('ESLResizeObserverTarget EventTarget adapter', () => {
       $host: el3
     };
 
-    const cb1_1 = jest.fn();
-    ESLResizeObserverTarget.for(el1).addEventListener(cb1_1);
-    expect(cb1_1).not.toBeCalled();
+    const cb11 = jest.fn();
+    ESLResizeObserverTarget.for(el1).addEventListener(cb11);
+    expect(cb11).not.toHaveBeenCalled();
 
-    const cb1_2 = jest.fn();
-    ESLResizeObserverTarget.for(el1).addEventListener(cb1_2);
-    expect(cb1_2).not.toBeCalled();
+    const cb12 = jest.fn();
+    ESLResizeObserverTarget.for(el1).addEventListener(cb12);
+    expect(cb12).not.toHaveBeenCalled();
 
-    const cb2_1 = jest.fn();
-    ESLResizeObserverTarget.for(el2).addEventListener(cb2_1);
-    expect(cb2_1).not.toBeCalled();
+    const cb21 = jest.fn();
+    ESLResizeObserverTarget.for(el2).addEventListener(cb21);
+    expect(cb21).not.toHaveBeenCalled();
 
-    const cb3_1 = jest.fn();
-    ESLResizeObserverTarget.for(mixin).addEventListener(cb3_1);
+    const cb31 = jest.fn();
+    ESLResizeObserverTarget.for(mixin).addEventListener(cb31);
 
     const fakeEntry = (el: Element) => ({
       target: el,
@@ -153,38 +153,38 @@ describe('ESLResizeObserverTarget EventTarget adapter', () => {
     });
 
     beforeEach(() => {
-      cb1_1.mockReset();
-      cb1_2.mockReset();
-      cb2_1.mockReset();
-      cb3_1.mockReset();
+      cb11.mockReset();
+      cb12.mockReset();
+      cb21.mockReset();
+      cb31.mockReset();
     });
 
     test('Dispatched change received by all subscribers', () => {
       const entry: ResizeObserverEntry = fakeEntry(el1);
 
       mock.callback.call(mock, [entry]);
-      expect(cb1_1).toBeCalled();
-      expect(cb1_2).toBeCalled();
-      expect(cb2_1).not.toBeCalled();
-      expect(cb3_1).not.toBeCalled();
+      expect(cb11).toHaveBeenCalled();
+      expect(cb12).toHaveBeenCalled();
+      expect(cb21).not.toHaveBeenCalled();
+      expect(cb31).not.toHaveBeenCalled();
     });
 
     test('Dispatched change received by correct target', () => {
       const entry: ResizeObserverEntry = fakeEntry(el2);
 
       mock.callback.call(mock, [entry]);
-      expect(cb2_1).toBeCalled();
-      expect(cb1_1).not.toBeCalled();
-      expect(cb1_2).not.toBeCalled();
-      expect(cb3_1).not.toBeCalled();
+      expect(cb21).toHaveBeenCalled();
+      expect(cb11).not.toHaveBeenCalled();
+      expect(cb12).not.toHaveBeenCalled();
+      expect(cb31).not.toHaveBeenCalled();
     });
 
     test('Dispatched change produces an Event instance', () => {
       const entry: ResizeObserverEntry = fakeEntry(el1);
 
       mock.callback.call(mock, [entry]);
-      expect(cb1_1).lastCalledWith(expect.any(Event));
-      expect(cb1_1).lastCalledWith(expect.objectContaining({type: 'resize'}));
+      expect(cb11).toHaveBeenLastCalledWith(expect.any(Event));
+      expect(cb11).toHaveBeenLastCalledWith(expect.objectContaining({type: 'resize'}));
     });
 
     test.each([
@@ -195,21 +195,21 @@ describe('ESLResizeObserverTarget EventTarget adapter', () => {
     ])('Dispatched change contains correct %s', (name: keyof ResizeObserverEntry) => {
       const entry: ResizeObserverEntry = fakeEntry(el1);
       mock.callback.call(mock, [entry]);
-      expect(cb1_1).lastCalledWith(expect.objectContaining({[name]: entry[name]}));
+      expect(cb11).toHaveBeenLastCalledWith(expect.objectContaining({[name]: entry[name]}));
     });
 
     test('Dispatched change produces correct event target', () => {
       const entry: ResizeObserverEntry = fakeEntry(el2);
 
       mock.callback.call(mock, [entry]);
-      expect(cb2_1).lastCalledWith(expect.objectContaining({target: el2}));
+      expect(cb21).toHaveBeenLastCalledWith(expect.objectContaining({target: el2}));
     });
 
     test('Dispatched change produces correct event target for ESLDomElementRelated', () => {
       const entry: ResizeObserverEntry = fakeEntry(el3);
 
       mock.callback.call(mock, [entry]);
-      expect(cb3_1).lastCalledWith(expect.objectContaining({target: el3}));
+      expect(cb31).toHaveBeenLastCalledWith(expect.objectContaining({target: el3}));
     });
   });
 });
diff --git a/src/modules/esl-utils/misc/object/test/path.test.ts b/src/modules/esl-utils/misc/object/test/path.test.ts
index ad89f25fe..b620f9723 100644
--- a/src/modules/esl-utils/misc/object/test/path.test.ts
+++ b/src/modules/esl-utils/misc/object/test/path.test.ts
@@ -6,7 +6,10 @@ describe('misc/object: path', () => {
   const y = Symbol();
 
   describe('parseKeys', () => {
-    const normalize = (keys: PathKeyDef[]) => keys.map((key) => { key.isIndex = !!key.isIndex; return key;});
+    const normalize = (keys: PathKeyDef[]) => keys.map((key) => {
+      key.isIndex = !!key.isIndex;
+      return key;
+    });
 
     describe('simple', () => {
       test.each([