@zalib/linter 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,31 +6,40 @@ npm i --save-dev --save-exact @zalib/linter
6
6
  ## Подключение конфигов
7
7
 
8
8
  ### eslint
9
- Пример конфига **.eslintrc.js** из корня сервиса.
9
+ Начиная с версии **eslint@9** изменился подход к файлам настройки. Файлы .eslintignore и .eslint.js заменяются одним файлом eslint.config.js (по умолчанию). Пример конфига **eslint.config.js** из корня сервиса.
10
10
  ```
11
- const jsEslintConfig = require('@zalib/linter/eslint/node-js');
12
- const tsEslintConfig = require('@zalib/linter/eslint/node-ts');
11
+ const { defineConfig } = require('eslint/config');
13
12
 
14
- module.exports = {
15
- overrides: [
16
- {
17
- ...jsEslintConfig,
18
- files: ['*.js'],
19
- },
20
- {
21
- ...tsEslintConfig,
22
- files: ['*.ts'],
23
- parser: '@typescript-eslint/parser',
24
- parserOptions: {
25
- include: ['./src/**/*.ts', './test/**/*.ts'],
26
- project: './tsconfig.json',
27
- },
28
- },
29
- ],
30
- root: true,
31
- };
13
+ module.exports = defineConfig([
14
+ {
15
+ ignores: ['**/node_modules/**', '**/dist/**'],
16
+ },
17
+ ...require('@zalib/linter/eslint/node')(),
18
+ ...require('@zalib/linter/eslint/node-ts')(), // если нужен
19
+ ...require('@zalib/linter/eslint/nestjs')(), // если нужен
20
+ ]);
32
21
  ```
33
22
 
23
+ Если необходимо ограничить путь к файлам только определенными директориями или для TS-проекта файл tsconfig.json имеет специфический путь или имя, тогда подключение нужно проводить с указанием необходимых параметров:
24
+ ```
25
+ const { defineConfig } = require('eslint/config');
26
+
27
+ module.exports = defineConfig([
28
+ {
29
+ ignores: ['**/node_modules/**', '**/dist/**'],
30
+ },
31
+ ...require('@zalib/linter/eslint/node')({
32
+ files: ['src/**/*.js', 'src/**/*.ts'] // или просто ['src/**/*.js']
33
+ }),
34
+ ...require('@zalib/linter/eslint/node-ts')({
35
+ files: ['src/**/*.ts', 'examples/**/*.ts'],
36
+ tsconfig: './tsconfig.dev.json'
37
+ }),
38
+ ]);
39
+ ```
40
+
41
+ **Внимание!** Встроенные наборы правил экспортируются в виде массивов, поэтому нужно использовать спред синтаксис.
42
+
34
43
  ### prettier
35
44
  Добавить в **package.json** в корневую секцию:
36
45
  ```
@@ -43,9 +52,9 @@ module.exports = {
43
52
  Добавить в **package.json** в секцию **scripts**:
44
53
  ```
45
54
  "format": "prettier --write 'src/**/*.ts'",
46
- "lint": "eslint 'src/**/*.{ts,js}' --quiet",
47
- "lint:fix": "eslint 'src/**/*.{js,ts}' --quiet --fix",
48
- "lint:warns": "eslint 'src/**/*.ts' --max-warnings 0",
55
+ "lint": "eslint --quiet",
56
+ "lint:fix": "eslint --quiet --fix",
57
+ "lint:warns": "eslint --max-warnings 0",
49
58
  ```
50
59
 
51
60
  ### Автоматизация при работе в репозитории
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Хелпер для проверки установки плагина
3
+ */
4
+ const isModuleInstalled = (moduleName) => {
5
+ try {
6
+ require.resolve(moduleName);
7
+
8
+ return true;
9
+ } catch {
10
+ return false;
11
+ }
12
+ };
13
+
14
+ module.exports = {
15
+ isModuleInstalled,
16
+ };
@@ -0,0 +1,26 @@
1
+ const nestjsPlugin = require('./plugin-nestjs');
2
+
3
+ module.exports = ({ files } = {}) => [
4
+ {
5
+ files: files || ['**/*.controller.ts'],
6
+ plugins: {
7
+ nestjs: nestjsPlugin,
8
+ },
9
+ rules: {
10
+ '@typescript-eslint/no-restricted-imports': [
11
+ 'error',
12
+ {
13
+ paths: [
14
+ {
15
+ allowTypeImports: true,
16
+ importNames: ['Controller'],
17
+ message: 'Use @ApiController decorator',
18
+ name: '@nestjs/common',
19
+ },
20
+ ],
21
+ },
22
+ ],
23
+ 'nestjs/api-response': 'error',
24
+ },
25
+ },
26
+ ];
package/eslint/node-ts.js CHANGED
@@ -1,4 +1,11 @@
1
- const nodeBaseConfig = require('./node');
1
+ /* eslint-disable sort-keys */
2
+ /* eslint-disable max-lines-per-function */
3
+
4
+ const tsParser = require('@typescript-eslint/parser');
5
+ const tsEslintPlugin = require('@typescript-eslint/eslint-plugin');
6
+ const tsImportSortPlugin = require('eslint-plugin-simple-import-sort');
7
+ const tsSortPlugin = require('eslint-plugin-typescript-sort');
8
+ const tsImportPlugin = require('eslint-plugin-import');
2
9
 
3
10
  function eslintMembersGroup(suffix) {
4
11
  return [
@@ -28,31 +35,40 @@ function eslintMembersGroup(suffix) {
28
35
  ];
29
36
  }
30
37
 
31
- module.exports = {
32
- env: {
33
- ...nodeBaseConfig.env,
34
- },
35
- extends: [
36
- ...nodeBaseConfig.extends,
37
- 'typescript/base',
38
- 'plugin:@typescript-eslint/recommended',
39
- 'plugin:@typescript-eslint/recommended-requiring-type-checking',
40
- 'plugin:import/errors',
41
- 'plugin:import/typescript',
42
- 'plugin:import/warnings',
43
- ],
44
- files: ['*.ts'],
45
- parser: '@typescript-eslint/parser',
46
- plugins: [
47
- ...nodeBaseConfig.plugins,
48
- '@typescript-eslint',
49
- 'simple-import-sort',
50
- 'typescript-sort',
51
- ],
52
- rules: {
53
- ...nodeBaseConfig.rules,
54
- '@typescript-eslint/await-thenable': 1,
55
- '@typescript-eslint/brace-style': 0,
38
+ module.exports = ({ files, tsconfig } = {}) => [
39
+ {
40
+ files: files || ['**/*.ts'],
41
+ languageOptions: {
42
+ parser: tsParser,
43
+ parserOptions: {
44
+ ecmaFeatures: { modules: true },
45
+ project: tsconfig || './tsconfig.json',
46
+ sourceType: 'module',
47
+ },
48
+ },
49
+ plugins: {
50
+ '@typescript-eslint': tsEslintPlugin,
51
+ 'simple-import-sort': tsImportSortPlugin,
52
+ 'typescript-sort': tsSortPlugin,
53
+ import: tsImportPlugin,
54
+ },
55
+ settings: {
56
+ 'import/resolver': {
57
+ typescript: {
58
+ project: tsconfig || './tsconfig.json',
59
+ },
60
+ },
61
+ },
62
+ rules: {
63
+ ...tsEslintPlugin.configs.recommended.rules,
64
+ ...tsEslintPlugin.configs['recommended-requiring-type-checking'].rules,
65
+ ...tsImportPlugin.configs.errors.rules,
66
+ ...tsImportPlugin.configs.warnings.rules,
67
+ ...tsImportPlugin.configs.typescript.rules,
68
+
69
+ '@typescript-eslint/await-thenable': 'warn',
70
+ '@typescript-eslint/brace-style': 'off',
71
+ /*
56
72
  '@typescript-eslint/comma-dangle': [
57
73
  2,
58
74
  {
@@ -66,215 +82,208 @@ module.exports = {
66
82
  tuples: 'always-multiline',
67
83
  },
68
84
  ],
69
- '@typescript-eslint/explicit-function-return-type': [
70
- 'error',
71
- {
72
- allowExpressions: true,
73
- },
74
- ],
75
- '@typescript-eslint/explicit-member-accessibility': [
76
- 2,
77
- {
78
- accessibility: 'explicit',
79
- overrides: {
80
- constructors: 'no-public',
85
+ */
86
+ '@typescript-eslint/explicit-function-return-type': [
87
+ 'error',
88
+ {
89
+ allowExpressions: true,
81
90
  },
82
- },
83
- ],
84
- '@typescript-eslint/explicit-module-boundary-types': 2,
85
- '@typescript-eslint/indent': 0,
86
- '@typescript-eslint/member-ordering': [
87
- 'error',
88
- {
89
- default: [
90
- // Index signature
91
- 'signature',
92
- 'call-signature',
91
+ ],
92
+ '@typescript-eslint/explicit-member-accessibility': [
93
+ 'error',
94
+ {
95
+ accessibility: 'explicit',
96
+ overrides: {
97
+ constructors: 'no-public',
98
+ },
99
+ },
100
+ ],
101
+ '@typescript-eslint/explicit-module-boundary-types': 'error',
102
+ '@typescript-eslint/indent': 'off',
103
+ '@typescript-eslint/member-ordering': [
104
+ 'error',
105
+ {
106
+ default: [
107
+ // Index signature
108
+ 'signature',
109
+ 'call-signature',
93
110
 
94
- // Fields
95
- ...eslintMembersGroup('field'),
111
+ // Fields
112
+ ...eslintMembersGroup('field'),
96
113
 
97
- // Static initialization
98
- 'static-initialization',
114
+ // Static initialization
115
+ 'static-initialization',
99
116
 
100
- // Constructors
101
- 'public-constructor',
102
- 'protected-constructor',
103
- 'private-constructor',
117
+ // Constructors
118
+ 'public-constructor',
119
+ 'protected-constructor',
120
+ 'private-constructor',
104
121
 
105
- 'constructor',
122
+ 'constructor',
106
123
 
107
- // Getters
108
- ...eslintMembersGroup('get'),
124
+ // Getters
125
+ ...eslintMembersGroup('get'),
109
126
 
110
- // Setters
111
- ...eslintMembersGroup('set'),
127
+ // Setters
128
+ ...eslintMembersGroup('set'),
112
129
 
113
- // Methods
114
- ...eslintMembersGroup('method'),
115
- ],
116
- },
117
- ],
118
- '@typescript-eslint/naming-convention': [
119
- 2,
120
- {
121
- format: ['camelCase'],
122
- leadingUnderscore: 'forbid',
123
- selector: 'default',
124
- trailingUnderscore: 'forbid',
125
- },
126
- {
127
- format: null,
128
- modifiers: ['requiresQuotes'],
129
- selector: [
130
- 'accessor',
131
- 'classMethod',
132
- 'classProperty',
133
- 'enumMember',
134
- 'objectLiteralMethod',
135
- 'objectLiteralProperty',
136
- 'typeMethod',
137
- 'typeProperty',
138
- ],
139
- },
140
- {
141
- format: null,
142
- modifiers: ['destructured', 'unused'],
143
- selector: 'variable',
144
- },
145
- {
146
- format: ['camelCase', 'UPPER_CASE'],
147
- leadingUnderscore: 'forbid',
148
- selector: 'variable',
149
- trailingUnderscore: 'forbid',
150
- },
151
- {
152
- format: ['camelCase', 'snake_case'],
153
- leadingUnderscore: 'forbid',
154
- selector: 'property',
155
- trailingUnderscore: 'forbid',
156
- },
157
- {
158
- format: ['camelCase'],
159
- leadingUnderscore: 'allow',
160
- selector: 'parameter',
161
- trailingUnderscore: 'forbid',
162
- },
163
- {
164
- format: ['PascalCase'],
165
- leadingUnderscore: 'forbid',
166
- selector: 'typeLike',
167
- trailingUnderscore: 'forbid',
168
- },
169
- {
170
- format: ['PascalCase', 'UPPER_CASE'],
171
- leadingUnderscore: 'forbid',
172
- selector: 'enum',
173
- trailingUnderscore: 'forbid',
174
- },
175
- {
176
- format: ['PascalCase', 'UPPER_CASE'],
177
- leadingUnderscore: 'forbid',
178
- selector: 'enumMember',
179
- trailingUnderscore: 'forbid',
180
- },
181
- // Правило, разрешающее написание "_id" с нижним подчеркиванием
182
- {
183
- filter: {
184
- match: true,
185
- regex: '^_id$',
130
+ // Methods
131
+ ...eslintMembersGroup('method'),
132
+ ],
186
133
  },
187
- format: null,
188
- leadingUnderscore: 'allow',
189
- selector: 'typeProperty',
190
- trailingUnderscore: 'forbid',
191
- },
192
- ],
193
- '@typescript-eslint/no-base-to-string': 0,
194
- '@typescript-eslint/no-empty-function': [
195
- 2,
196
- {
197
- allow: [
198
- 'constructors',
199
- 'private-constructors',
200
- 'protected-constructors',
201
- 'decoratedFunctions',
202
- 'overrideMethods',
203
- 'setters',
204
- ]
205
- }
206
- ],
207
- '@typescript-eslint/no-explicit-any': 2,
208
- '@typescript-eslint/no-floating-promises': 0,
209
- '@typescript-eslint/no-magic-numbers': [
210
- 1,
211
- {
212
- ignore: [-1, 0, 1],
213
- ignoreArrayIndexes: true,
214
- ignoreDefaultValues: true,
215
- ignoreEnums: true,
216
- ignoreNumericLiteralTypes: true,
217
- ignoreReadonlyClassProperties: true,
218
- },
219
- ],
220
- '@typescript-eslint/no-misused-promises': 0,
221
- '@typescript-eslint/no-unsafe-argument': 0,
222
- '@typescript-eslint/no-unsafe-assignment': 0,
223
- '@typescript-eslint/no-unsafe-call': 0,
224
- '@typescript-eslint/no-unsafe-member-access': 0,
225
- '@typescript-eslint/no-unsafe-return': 0,
226
- '@typescript-eslint/require-await': 1,
227
- '@typescript-eslint/restrict-template-expressions': [
228
- 2,
229
- {
230
- allowAny: true,
231
- allowBoolean: true,
232
- allowNullish: true,
233
- allowNumber: true,
234
- },
235
- ],
236
- '@typescript-eslint/return-await': [2, 'in-try-catch'],
237
- 'import/default': 2,
238
- 'import/export': 2,
239
- 'import/first': 2,
240
- 'import/named': 2,
241
- 'import/namespace': 2,
242
- 'import/newline-after-import': 2,
243
- 'import/no-cycle': 2,
244
- 'import/no-extraneous-dependencies': [
245
- 2,
246
- {
247
- devDependencies: true,
248
- },
249
- ],
250
- 'import/order': 0,
251
- 'import/prefer-default-export': 0,
252
- 'max-classes-per-file': 0,
253
- 'simple-import-sort/imports': [
254
- 2,
255
- {
256
- groups: [
257
- // Side effect imports.
258
- ['^\\u0000'],
259
- // Packages.
260
- // Things that start with a letter (or digit or underscore), or `@` followed by a letter.
261
- ['^@?\\w'],
262
- // Absolute imports and other imports such as Vue-style `@/foo`.
263
- // Anything not matched in another group.
264
- ['^'],
265
- // Relative imports.
266
- // Anything that starts with a dot.
267
- ['^\\.'],
268
- // Interfaces, typings
269
- ['^\\..*(\\/|\\.)(interface|types$|typings$)'],
270
- // Constants
271
- ['^\\..*(\\/|\\.)(constant|config)'],
272
- ],
273
- },
274
- ],
275
- 'sort-imports': 0,
276
- 'typescript-sort/interface': 2,
277
- 'typescript-sort/type': 2,
278
- 'typescript-sort/enum': 2,
134
+ ],
135
+ '@typescript-eslint/naming-convention': [
136
+ 'error',
137
+ {
138
+ format: ['camelCase'],
139
+ leadingUnderscore: 'forbid',
140
+ selector: 'default',
141
+ trailingUnderscore: 'forbid',
142
+ },
143
+ {
144
+ format: null,
145
+ modifiers: ['requiresQuotes'],
146
+ selector: [
147
+ 'accessor',
148
+ 'classMethod',
149
+ 'classProperty',
150
+ 'enumMember',
151
+ 'objectLiteralMethod',
152
+ 'objectLiteralProperty',
153
+ 'typeMethod',
154
+ 'typeProperty',
155
+ ],
156
+ },
157
+ {
158
+ format: null,
159
+ modifiers: ['destructured', 'unused'],
160
+ selector: 'variable',
161
+ },
162
+ {
163
+ format: ['camelCase', 'UPPER_CASE'],
164
+ leadingUnderscore: 'forbid',
165
+ selector: 'variable',
166
+ trailingUnderscore: 'forbid',
167
+ },
168
+ {
169
+ format: ['camelCase', 'snake_case'],
170
+ leadingUnderscore: 'forbid',
171
+ selector: 'property',
172
+ trailingUnderscore: 'forbid',
173
+ },
174
+ {
175
+ format: ['camelCase'],
176
+ leadingUnderscore: 'allow',
177
+ selector: 'parameter',
178
+ trailingUnderscore: 'forbid',
179
+ },
180
+ {
181
+ format: ['PascalCase'],
182
+ leadingUnderscore: 'forbid',
183
+ selector: 'typeLike',
184
+ trailingUnderscore: 'forbid',
185
+ },
186
+ {
187
+ format: ['PascalCase', 'UPPER_CASE'],
188
+ leadingUnderscore: 'forbid',
189
+ selector: 'enum',
190
+ trailingUnderscore: 'forbid',
191
+ },
192
+ {
193
+ format: ['PascalCase', 'UPPER_CASE'],
194
+ leadingUnderscore: 'forbid',
195
+ selector: 'enumMember',
196
+ trailingUnderscore: 'forbid',
197
+ },
198
+ ],
199
+ '@typescript-eslint/no-base-to-string': 'off',
200
+ '@typescript-eslint/no-empty-function': [
201
+ 'error',
202
+ {
203
+ allow: [
204
+ 'constructors',
205
+ 'private-constructors',
206
+ 'protected-constructors',
207
+ 'decoratedFunctions',
208
+ 'overrideMethods',
209
+ 'setters',
210
+ ],
211
+ },
212
+ ],
213
+ '@typescript-eslint/no-explicit-any': 'error',
214
+ '@typescript-eslint/no-floating-promises': 'off',
215
+ '@typescript-eslint/no-magic-numbers': [
216
+ 'warn',
217
+ {
218
+ ignore: [-1, 0, 1],
219
+ ignoreArrayIndexes: true,
220
+ ignoreDefaultValues: true,
221
+ ignoreEnums: true,
222
+ ignoreNumericLiteralTypes: true,
223
+ ignoreReadonlyClassProperties: true,
224
+ },
225
+ ],
226
+ '@typescript-eslint/no-misused-promises': 'off',
227
+ '@typescript-eslint/no-unsafe-argument': 'off',
228
+ '@typescript-eslint/no-unsafe-assignment': 'off',
229
+ '@typescript-eslint/no-unsafe-call': 'off',
230
+ '@typescript-eslint/no-unsafe-member-access': 'off',
231
+ '@typescript-eslint/no-unsafe-return': 'off',
232
+ '@typescript-eslint/require-await': 'warn',
233
+ '@typescript-eslint/restrict-template-expressions': [
234
+ 'error',
235
+ {
236
+ allowAny: true,
237
+ allowBoolean: true,
238
+ allowNullish: true,
239
+ allowNumber: true,
240
+ },
241
+ ],
242
+ '@typescript-eslint/return-await': ['error', 'in-try-catch'],
243
+ 'import/default': 'error',
244
+ 'import/export': 'error',
245
+ 'import/extensions': ['error', 'ignorePackages', { ts: 'never' }],
246
+ 'import/first': 'error',
247
+ 'import/named': 'error',
248
+ 'import/namespace': 'error',
249
+ 'import/newline-after-import': 'error',
250
+ 'import/no-cycle': 'error',
251
+ 'import/no-extraneous-dependencies': [
252
+ 'error',
253
+ {
254
+ devDependencies: true,
255
+ },
256
+ ],
257
+ 'import/no-unresolved': 'error',
258
+ 'import/order': 'off',
259
+ 'import/prefer-default-export': 'off',
260
+ 'max-classes-per-file': 'off',
261
+ 'simple-import-sort/imports': [
262
+ 'error',
263
+ {
264
+ groups: [
265
+ // Side effect imports.
266
+ ['^\\u0000'],
267
+ // Packages.
268
+ // Things that start with a letter (or digit or underscore), or `@` followed by a letter.
269
+ ['^@?\\w'],
270
+ // Absolute imports and other imports such as Vue-style `@/foo`.
271
+ // Anything not matched in another group.
272
+ ['^'],
273
+ // Relative imports.
274
+ // Anything that starts with a dot.
275
+ ['^\\.'],
276
+ // Interfaces, typings
277
+ ['^\\..*(\\/|\\.)(interface|types$|typings$)'],
278
+ // Constants
279
+ ['^\\..*(\\/|\\.)(constant|config)'],
280
+ ],
281
+ },
282
+ ],
283
+ 'sort-imports': 'off',
284
+ 'typescript-sort/interface': 'error',
285
+ 'typescript-sort/type': 'error',
286
+ 'typescript-sort/enum': 'error',
287
+ },
279
288
  },
280
- };
289
+ ];
package/eslint/node.js CHANGED
@@ -1,91 +1,96 @@
1
- module.exports = {
2
- env: {
3
- browser: false,
4
- es6: true,
5
- jest: true,
6
- 'jest/globals': true,
7
- node: true,
1
+ const jestPlugin = require('eslint-plugin-jest');
2
+ const prettierPlugin = require('eslint-plugin-prettier');
3
+ const { isModuleInstalled } = require('./helpers');
4
+
5
+ module.exports = ({ files } = {}) => [
6
+ {
7
+ files: files || ['**/*.js', '**/*.ts'],
8
+ plugins: {
9
+ jest: jestPlugin,
10
+ prettier: prettierPlugin,
11
+ },
12
+ rules: {
13
+ ...jestPlugin.configs.recommended.rules,
14
+ ...jestPlugin.configs.style.rules,
15
+ ...prettierPlugin.configs.recommended.rules,
16
+
17
+ 'arrow-parens': [
18
+ 'warn',
19
+ 'always',
20
+ {
21
+ requireForBlockBody: true,
22
+ },
23
+ ],
24
+ 'class-methods-use-this': 'off',
25
+ 'comma-dangle': [
26
+ 'error',
27
+ {
28
+ arrays: 'always-multiline',
29
+ exports: 'always-multiline',
30
+ functions: 'always-multiline',
31
+ imports: 'always-multiline',
32
+ objects: 'always-multiline',
33
+ },
34
+ ],
35
+ complexity: ['error', { max: 10 }],
36
+ 'function-paren-newline': 'off',
37
+ 'implicit-arrow-linebreak': 'off',
38
+ 'import/extensions': 'off',
39
+ 'import/no-unresolved': 'off',
40
+ 'jest/no-deprecated-functions': isModuleInstalled('jest')
41
+ ? 'error'
42
+ : 'off',
43
+ 'max-lines': [
44
+ 'error',
45
+ {
46
+ max: 500,
47
+ skipBlankLines: false,
48
+ skipComments: true,
49
+ },
50
+ ],
51
+ 'max-lines-per-function': [
52
+ 'error',
53
+ {
54
+ max: 200,
55
+ skipBlankLines: false,
56
+ skipComments: true,
57
+ },
58
+ ],
59
+ 'max-params': ['error', { max: 3 }],
60
+ 'no-await-in-loop': 'off',
61
+ 'no-continue': 'off',
62
+ 'no-empty-function': 'off',
63
+ 'no-plusplus': [
64
+ 'error',
65
+ {
66
+ allowForLoopAfterthoughts: true,
67
+ },
68
+ ],
69
+ 'no-promise-executor-return': 'off',
70
+ 'no-restricted-syntax': 'off',
71
+ 'padding-line-between-statements': [
72
+ 'error',
73
+ {
74
+ blankLine: 'always',
75
+ next: '*',
76
+ prev: ['block-like'],
77
+ },
78
+ {
79
+ blankLine: 'always',
80
+ next: 'if',
81
+ prev: '*',
82
+ },
83
+ {
84
+ blankLine: 'always',
85
+ next: '*',
86
+ prev: 'if',
87
+ },
88
+ ],
89
+ 'prettier/prettier': 'error',
90
+ 'require-await': 'off',
91
+ 'sort-imports': 'error',
92
+ 'sort-keys': 'error',
93
+ strict: 'off',
94
+ },
8
95
  },
9
- extends: [
10
- 'eslint:recommended',
11
- 'plugin:jest/recommended',
12
- 'plugin:jest/style',
13
- 'prettier',
14
- 'plugin:prettier/recommended',
15
- ],
16
- plugins: ['jest'],
17
- rules: {
18
- 'arrow-parens': [
19
- 1,
20
- 'always',
21
- {
22
- requireForBlockBody: true,
23
- },
24
- ],
25
- 'class-methods-use-this': 0,
26
- 'comma-dangle': [
27
- 'error',
28
- {
29
- arrays: 'always-multiline',
30
- exports: 'always-multiline',
31
- functions: 'always-multiline',
32
- imports: 'always-multiline',
33
- objects: 'always-multiline',
34
- },
35
- ],
36
- complexity: [2, { max: 10 }],
37
- 'function-paren-newline': 0,
38
- 'implicit-arrow-linebreak': 0,
39
- 'import/extensions': 0,
40
- 'import/no-unresolved': 0,
41
- 'max-lines': [
42
- 2,
43
- {
44
- max: 500,
45
- skipBlankLines: false,
46
- skipComments: true,
47
- },
48
- ],
49
- 'max-lines-per-function': [
50
- 2,
51
- {
52
- max: 200,
53
- skipBlankLines: false,
54
- skipComments: true,
55
- },
56
- ],
57
- 'max-params': [2, { max: 3 }],
58
- 'no-await-in-loop': 0,
59
- 'no-continue': 0,
60
- 'no-empty-function': 0,
61
- 'no-plusplus': [
62
- 2,
63
- {
64
- allowForLoopAfterthoughts: true,
65
- },
66
- ],
67
- 'no-promise-executor-return': 0,
68
- 'no-restricted-syntax': 0,
69
- 'padding-line-between-statements': [
70
- 2,
71
- {
72
- blankLine: 'always',
73
- next: '*',
74
- prev: ['block-like'],
75
- },
76
- {
77
- blankLine: 'always',
78
- next: 'if',
79
- prev: '*',
80
- },
81
- {
82
- blankLine: 'always',
83
- next: '*',
84
- prev: 'if',
85
- },
86
- ],
87
- 'require-await': 0,
88
- 'sort-imports': 2,
89
- 'sort-keys': 2,
90
- },
91
- };
96
+ ];
@@ -0,0 +1,64 @@
1
+ /* eslint-disable complexity */
2
+
3
+ const controllerMethods = new Set(['Get', 'Post', 'Put', 'Delete', 'Patch']);
4
+ const decoratorTypes = new Set(['ClassDeclaration', 'MethodDefinition']);
5
+
6
+ module.exports = {
7
+ create(context) {
8
+ // собирает декораторы узла
9
+ function getNodeDecorators(node) {
10
+ return decoratorTypes.has(node.type) && node.decorators
11
+ ? node.decorators
12
+ .map(({ expression }) => expression.callee?.name)
13
+ .filter(Boolean)
14
+ : [];
15
+ }
16
+
17
+ // проверка на игнорирование метода
18
+ function ignoreControllerMethod(node) {
19
+ if (node.key.name === 'constructor') return true;
20
+
21
+ const methodDecorators = getNodeDecorators(node);
22
+ const parentDecorators = getNodeDecorators(node.parent.parent);
23
+
24
+ // игнорировать, если это не метод @ApiController
25
+ if (!parentDecorators.includes('ApiController')) return true;
26
+
27
+ // игнорировать, если есть декоратор @AnyResponse
28
+ if (methodDecorators.includes('AnyResponse')) return true;
29
+
30
+ // игнорировать, если нет декоратора роутинга
31
+ return !node.decorators?.some(({ expression }) =>
32
+ controllerMethods.has(expression.callee?.name),
33
+ );
34
+ }
35
+
36
+ // рекурсивный проход по описанию типа
37
+ function isApiResponseType(typeAnnotation, level = 0) {
38
+ if (level === 0 && typeAnnotation?.typeName?.name === 'Promise') {
39
+ const typeArgument = typeAnnotation.typeArguments?.params[0];
40
+
41
+ return isApiResponseType(typeArgument, level + 1);
42
+ }
43
+
44
+ if (typeAnnotation?.type === 'TSVoidKeyword') return true;
45
+
46
+ return (
47
+ typeAnnotation?.type === 'TSTypeReference' &&
48
+ typeAnnotation.typeName?.name === 'ApiResponse'
49
+ );
50
+ }
51
+
52
+ return {
53
+ MethodDefinition(node) {
54
+ if (ignoreControllerMethod(node)) return;
55
+
56
+ if (!isApiResponseType(node.value.returnType?.typeAnnotation)) {
57
+ const message = 'The return type must be "ApiResponse<T>" or "void"';
58
+
59
+ context.report({ message, node });
60
+ }
61
+ },
62
+ };
63
+ },
64
+ };
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ rules: {
3
+ 'api-response': require('./api-response'),
4
+ },
5
+ };
@@ -0,0 +1,6 @@
1
+ const { defineConfig } = require('eslint/config');
2
+
3
+ module.exports = defineConfig([
4
+ { ignores: ['**/node_modules/**'] },
5
+ ...require('./eslint/node')(),
6
+ ]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalib/linter",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "Linter configs",
5
5
  "author": "https://github.com/wsp-repo/",
6
6
  "license": "UNLICENSED",
@@ -20,7 +20,9 @@
20
20
  },
21
21
  "scripts": {
22
22
  "build": "exit 0",
23
- "lint": "exit 0",
23
+ "lint": "eslint --quiet",
24
+ "lint:fix": "eslint --quiet --fix",
25
+ "lint:warns": "eslint --max-warnings 0",
24
26
  "release:patch": "npm version patch",
25
27
  "release:minor": "npm version minor",
26
28
  "release:major": "npm version major",
@@ -30,17 +32,14 @@
30
32
  "@typescript-eslint/eslint-plugin": "8.32.1",
31
33
  "@typescript-eslint/parser": "8.32.1",
32
34
  "eslint": "9.27.0",
33
- "eslint-config-base": "1.0.0",
34
35
  "eslint-config-prettier": "10.1.5",
35
- "eslint-config-typescript": "3.0.0",
36
+ "eslint-import-resolver-typescript": "4.3.5",
36
37
  "eslint-plugin-import": "2.31.0",
37
38
  "eslint-plugin-jest": "28.11.0",
38
39
  "eslint-plugin-prettier": "5.4.0",
39
40
  "eslint-plugin-simple-import-sort": "12.1.1",
40
- "jest": "29.7.0",
41
+ "eslint-plugin-typescript-sort": "0.1.11",
41
42
  "prettier": "3.5.3"
42
43
  },
43
- "devDependencies": {
44
- "typescript": "5.8.3"
45
- }
44
+ "prettier": "./prettier"
46
45
  }
package/.eslintrc.js DELETED
@@ -1,9 +0,0 @@
1
- module.exports = {
2
- overrides: [
3
- {
4
- extends: ['./eslint/node-js'],
5
- files: ['*.js'],
6
- },
7
- ],
8
- root: true,
9
- };
package/eslint/node-js.js DELETED
@@ -1,23 +0,0 @@
1
- const nodeBaseConfig = require('./node');
2
-
3
- module.exports = {
4
- env: {
5
- ...nodeBaseConfig.env,
6
- },
7
- extends: [
8
- ...nodeBaseConfig.extends,
9
- 'eslint:recommended',
10
- 'plugin:jest/recommended',
11
- 'plugin:jest/style',
12
- 'prettier',
13
- 'plugin:prettier/recommended'
14
- ],
15
- files: ['*.js'],
16
- plugins: [
17
- ...nodeBaseConfig.plugins,
18
- ],
19
- rules: {
20
- ...nodeBaseConfig.rules,
21
- strict: 0,
22
- },
23
- };
@@ -1 +0,0 @@
1
- module.exports = require('./prettier');