Skip to content

Commit 84a037f

Browse files
committed
AG-45207 Improve ESLint config
Squashed commit of the following: commit 0de441e Author: scripthunter7 <d.tota@adguard.com> Date: Mon Sep 29 15:41:55 2025 +0200 improve eslint config
1 parent bf34dca commit 84a037f

29 files changed

+625
-234
lines changed

.eslintrc.cjs

Lines changed: 294 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,313 @@
1+
const path = require('node:path');
2+
3+
const MAX_LINE_LENGTH = 120;
4+
5+
/**
6+
* ESLint rules.
7+
*
8+
* @see {@link https://eslint.org/docs/v8.x/rules/}
9+
*/
10+
const ESLINT_RULES = {
11+
indent: 'off',
12+
'no-bitwise': 'off',
13+
'no-new': 'off',
14+
'no-continue': 'off',
15+
'arrow-body-style': 'off',
16+
17+
'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'],
18+
'no-constant-condition': ['error', { checkLoops: false }],
19+
'max-len': [
20+
'error',
21+
{
22+
code: MAX_LINE_LENGTH,
23+
comments: MAX_LINE_LENGTH,
24+
tabWidth: 4,
25+
ignoreUrls: true,
26+
ignoreTrailingComments: false,
27+
ignoreComments: false,
28+
},
29+
],
30+
// Sort members of import statements, e.g. `import { B, A } from 'module';` -> `import { A, B } from 'module';`
31+
// Note: imports themself are sorted by import/order rule
32+
'sort-imports': ['error', {
33+
ignoreCase: true,
34+
// Avoid conflict with import/order rule
35+
ignoreDeclarationSort: true,
36+
ignoreMemberSort: false,
37+
memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
38+
}],
39+
};
40+
141
/**
2-
* @file ESLint configuration based on Airbnb's with some modifications.
42+
* TypeScript ESLint rules.
43+
*
44+
* @see {@link https://typescript-eslint.io/rules/}
345
*/
46+
const TYPESCRIPT_ESLINT_RULES = {
47+
'@typescript-eslint/no-non-null-assertion': 'off',
48+
'@typescript-eslint/interface-name-prefix': 'off',
449

5-
// eslint-disable-next-line @typescript-eslint/no-var-requires
6-
const { join } = require('path');
50+
'@typescript-eslint/member-delimiter-style': 'error',
751

8-
const MAX_LINE_LENGTH = 120;
52+
'@typescript-eslint/indent': ['error', 4],
53+
'@typescript-eslint/explicit-member-accessibility': [
54+
'error',
55+
{
56+
accessibility: 'explicit',
57+
overrides: {
58+
accessors: 'explicit',
59+
constructors: 'no-public',
60+
methods: 'explicit',
61+
properties: 'off',
62+
parameterProperties: 'explicit',
63+
},
64+
},
65+
],
66+
// Force proper import and export of types
67+
'@typescript-eslint/consistent-type-imports': [
68+
'error',
69+
{
70+
prefer: 'type-imports',
71+
fixStyle: 'inline-type-imports',
72+
},
73+
],
74+
'@typescript-eslint/consistent-type-exports': [
75+
'error',
76+
{
77+
fixMixedExportsWithInlineTypeSpecifier: true,
78+
},
79+
],
80+
};
81+
82+
/**
83+
* Import plugin rules.
84+
*
85+
* @see {@link https://github.com/import-js/eslint-plugin-import/tree/main/docs/rules}
86+
*/
87+
const IMPORT_PLUGIN_RULES = {
88+
'import/prefer-default-export': 'off',
89+
90+
'import-newlines/enforce': ['error', 3, MAX_LINE_LENGTH],
91+
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
92+
// Split external and internal imports with an empty line
93+
'import/order': [
94+
'error',
95+
{
96+
groups: [
97+
// Built-in Node.js modules
98+
'builtin',
99+
// External packages
100+
'external',
101+
// Parent modules should be in the second place, e.g. `import { foo } from '../bar';`
102+
'parent',
103+
// Sibling modules should be in the third place, e.g. `import { foo } from './bar';`
104+
'sibling',
105+
// All other imports should be in the last place
106+
],
107+
alphabetize: { order: 'asc', caseInsensitive: true },
108+
'newlines-between': 'always',
109+
},
110+
],
111+
};
112+
113+
/**
114+
* JSDoc plugin rules.
115+
*
116+
* @see {@link https://github.com/gajus/eslint-plugin-jsdoc?tab=readme-ov-file#user-content-eslint-plugin-jsdoc-rules}
117+
*/
118+
const JSDOC_PLUGIN_RULES = {
119+
// Types are described in TypeScript
120+
'jsdoc/require-param-type': 'off',
121+
'jsdoc/no-undefined-types': 'off',
122+
'jsdoc/require-returns-type': 'off',
123+
124+
'jsdoc/require-param-description': 'error',
125+
'jsdoc/require-property-description': 'error',
126+
'jsdoc/require-returns-description': 'error',
127+
'jsdoc/require-returns': 'error',
128+
'jsdoc/require-param': 'error',
129+
'jsdoc/require-returns-check': 'error',
130+
131+
'jsdoc/check-tag-names': [
132+
'warn',
133+
{
134+
// Define additional tags
135+
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-tag-names.md#definedtags
136+
definedTags: ['note'],
137+
},
138+
],
139+
140+
'jsdoc/require-hyphen-before-param-description': ['error', 'never'],
141+
'jsdoc/require-jsdoc': [
142+
'error',
143+
{
144+
contexts: [
145+
'ClassDeclaration',
146+
'ClassProperty',
147+
'PropertyDefinition',
148+
'FunctionDeclaration',
149+
'MethodDefinition',
150+
],
151+
},
152+
],
153+
'jsdoc/require-description': [
154+
'error',
155+
{
156+
contexts: [
157+
'ClassDeclaration',
158+
'ClassProperty',
159+
'PropertyDefinition',
160+
'FunctionDeclaration',
161+
'MethodDefinition',
162+
],
163+
},
164+
],
165+
'jsdoc/require-description-complete-sentence': [
166+
'error',
167+
{
168+
abbreviations: [
169+
'e.g.',
170+
'i.e.',
171+
],
172+
},
173+
],
174+
'jsdoc/multiline-blocks': [
175+
'error',
176+
{
177+
noSingleLineBlocks: true,
178+
singleLineTags: [
179+
'inheritdoc',
180+
],
181+
},
182+
],
183+
'jsdoc/tag-lines': [
184+
'error',
185+
'any',
186+
{
187+
startLines: 1,
188+
},
189+
],
190+
'jsdoc/sort-tags': [
191+
'error',
192+
{
193+
linesBetween: 1,
194+
tagSequence: [
195+
{ tags: ['file'] },
196+
{ tags: ['template'] },
197+
{ tags: ['see'] },
198+
{ tags: ['param'] },
199+
{ tags: ['returns'] },
200+
{ tags: ['throws'] },
201+
{ tags: ['example'] },
202+
],
203+
},
204+
],
205+
};
206+
207+
/**
208+
* N plugin rules.
209+
*
210+
* @see {@link https://github.com/eslint-community/eslint-plugin-n?tab=readme-ov-file#-rules}
211+
*/
212+
const N_PLUGIN_RULES = {
213+
// Import plugin is enough, also, this rule requires extensions in ESM, but we use bundler resolution
214+
'n/no-missing-import': 'off',
215+
'n/no-unpublished-import': 'off',
216+
// Require using node protocol for node modules, e.g. `node:fs` instead of `fs`.
217+
'n/prefer-node-protocol': 'error',
218+
// Prefer `/promises` API for `fs` and `dns` modules, if the corresponding imports are used.
219+
'n/prefer-promises/fs': 'error',
220+
'n/prefer-promises/dns': 'error',
221+
};
222+
223+
/**
224+
* Boundaries plugin rules.
225+
*
226+
* @see {@link https://github.com/javierbrea/eslint-plugin-boundaries#readme}
227+
*/
228+
const BOUNDARIES_PLUGIN_RULES = {
229+
'boundaries/element-types': ['error', {
230+
default: 'allow',
231+
rules: [
232+
{
233+
from: 'client',
234+
allow: ['client'],
235+
},
236+
{
237+
from: 'server',
238+
allow: ['server'],
239+
},
240+
// TODO: Add more rules, like helpers only can import helpers, etc.
241+
],
242+
}],
243+
};
244+
245+
/**
246+
* Merges multiple rule sets into a single object.
247+
*
248+
* @param ruleSets The rule sets to merge.
249+
*
250+
* @returns The merged rule set.
251+
*/
252+
function mergeRules(...ruleSets) {
253+
const merged = {};
254+
for (const rules of ruleSets) {
255+
for (const [key, value] of Object.entries(rules)) {
256+
if (merged[key]) {
257+
throw new Error(`Duplicate ESLint rule: ${key}`);
258+
}
259+
merged[key] = value;
260+
}
261+
}
262+
return merged;
263+
}
9264

10265
module.exports = {
11266
root: true,
267+
parser: '@typescript-eslint/parser',
268+
parserOptions: {
269+
tsconfigRootDir: path.join(__dirname),
270+
project: 'tsconfig.eslint.json',
271+
},
272+
plugins: [
273+
'import',
274+
'import-newlines',
275+
'@typescript-eslint',
276+
'n',
277+
'boundaries',
278+
],
12279
extends: [
13-
'eslint:recommended',
14-
'plugin:@typescript-eslint/recommended',
15280
'airbnb-base',
16281
'airbnb-typescript/base',
282+
'plugin:@typescript-eslint/eslint-recommended',
17283
'plugin:jsdoc/recommended',
284+
'plugin:jsdoc/recommended-typescript',
285+
'plugin:n/recommended',
18286
],
19-
parser: '@typescript-eslint/parser',
20-
parserOptions: {
21-
tsconfigRootDir: join(__dirname),
22-
project: 'tsconfig.eslint.json',
23-
},
24-
plugins: ['import', '@typescript-eslint', 'import-newlines'],
25-
rules: {
26-
'max-len': [
27-
'error',
28-
{
29-
code: MAX_LINE_LENGTH,
30-
comments: MAX_LINE_LENGTH,
31-
tabWidth: 4,
32-
ignoreUrls: true,
33-
ignoreTrailingComments: false,
34-
ignoreComments: false,
35-
},
36-
],
37-
'@typescript-eslint/indent': [
38-
'error',
39-
4,
40-
{
41-
SwitchCase: 1,
42-
},
43-
],
44-
'jsdoc/multiline-blocks': ['error', { noSingleLineBlocks: true }],
45-
'import/prefer-default-export': 'off',
46-
'import-newlines/enforce': ['error', { items: 3, 'max-len': MAX_LINE_LENGTH }],
47-
// Split external and internal imports with an empty line
48-
'import/order': [
49-
'error',
50-
{
51-
groups: [
52-
['builtin', 'external'],
53-
],
54-
'newlines-between': 'always',
55-
},
56-
],
57-
// We can disable this, because we bundle everything
58-
'import/no-extraneous-dependencies': 'off',
59-
'no-restricted-syntax': ['error', 'LabeledStatement', 'WithStatement'],
60-
'no-continue': 'off',
61-
'jsdoc/require-param-type': 'off',
62-
'jsdoc/require-returns-type': 'off',
63-
'jsdoc/tag-lines': [
64-
'warn',
65-
'any',
66-
{
67-
startLines: 1,
68-
},
69-
],
70-
'jsdoc/check-tag-names': [
71-
'warn',
72-
{
73-
// Define additional tags
74-
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-tag-names.md#definedtags
75-
definedTags: ['note'],
76-
},
77-
],
78-
'arrow-body-style': 'off',
79-
'no-await-in-loop': 'off',
80-
// Force proper import and export of types
81-
'@typescript-eslint/consistent-type-imports': [
82-
'error',
287+
ignorePatterns: [
288+
'dist',
289+
'coverage',
290+
],
291+
settings: {
292+
'boundaries/elements': [
83293
{
84-
fixStyle: 'inline-type-imports',
294+
type: 'client',
295+
pattern: 'client',
296+
mode: 'folder',
85297
},
86-
],
87-
'@typescript-eslint/consistent-type-exports': [
88-
'error',
89298
{
90-
fixMixedExportsWithInlineTypeSpecifier: true,
299+
type: 'server',
300+
pattern: 'server',
301+
mode: 'folder',
91302
},
92303
],
93304
},
305+
rules: mergeRules(
306+
ESLINT_RULES,
307+
TYPESCRIPT_ESLINT_RULES,
308+
IMPORT_PLUGIN_RULES,
309+
JSDOC_PLUGIN_RULES,
310+
N_PLUGIN_RULES,
311+
BOUNDARIES_PLUGIN_RULES,
312+
),
94313
};

0 commit comments

Comments
 (0)