@zalib/linter 2.0.1 → 2.0.2

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,29 +6,35 @@ 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
+ ]);
20
+ ```
21
+
22
+ Если необходимо ограничить путь к файлам только определенными директориями или для TS-проекта файл tsconfig.json имеет специфический путь или имя, тогда подключение нужно проводить с указанием необходимых параметров:
23
+ ```
24
+ const { defineConfig } = require('eslint/config');
25
+
26
+ module.exports = defineConfig([
27
+ {
28
+ ignores: ['**/node_modules/**', '**/dist/**'],
29
+ },
30
+ require('@zalib/linter/eslint/node')({
31
+ files: ['src/**/*.js', 'src/**/*.ts'] // или просто ['src/**/*.js']
32
+ }),
33
+ require('@zalib/linter/eslint/node-ts')({
34
+ files: ['src/**/*.ts', 'examples/**/*.ts'],
35
+ tsconfig: './tsconfig.dev.json'
36
+ }),
37
+ ]);
32
38
  ```
33
39
 
34
40
  ### prettier
@@ -43,9 +49,9 @@ module.exports = {
43
49
  Добавить в **package.json** в секцию **scripts**:
44
50
  ```
45
51
  "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",
52
+ "lint": "eslint --quiet",
53
+ "lint:fix": "eslint --quiet --fix",
54
+ "lint:warns": "eslint --max-warnings 0",
49
55
  ```
50
56
 
51
57
  ### Автоматизация при работе в репозитории
@@ -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,11 @@
1
+ const nestjsPlugin = require('./plugin-nestjs');
2
+
3
+ module.exports = ({ files } = {}) => ({
4
+ files: files || ['**/*.controller.ts'],
5
+ plugins: {
6
+ nestjs: nestjsPlugin,
7
+ },
8
+ rules: {
9
+ 'nestjs/api-response': 'error',
10
+ },
11
+ });
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,42 @@ function eslintMembersGroup(suffix) {
28
35
  ];
29
36
  }
30
37
 
31
- module.exports = {
32
- env: {
33
- ...nodeBaseConfig.env,
38
+ module.exports = ({ files, tsconfig } = {}) => ({
39
+ files: files || ['**/*.ts'],
40
+ languageOptions: {
41
+ parser: tsParser,
42
+ parserOptions: {
43
+ ecmaFeatures: { modules: true },
44
+ project: tsconfig || './tsconfig.json',
45
+ sourceType: 'module',
46
+ },
47
+ },
48
+ plugins: {
49
+ '@typescript-eslint': tsEslintPlugin,
50
+ 'simple-import-sort': tsImportSortPlugin,
51
+ 'typescript-sort': tsSortPlugin,
52
+ import: tsImportPlugin,
53
+ },
54
+ settings: {
55
+ 'import/resolver': {
56
+ typescript: {
57
+ project: tsconfig || './tsconfig.json',
58
+ },
59
+ },
34
60
  },
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
61
  rules: {
53
- ...nodeBaseConfig.rules,
54
- '@typescript-eslint/await-thenable': 1,
55
- '@typescript-eslint/brace-style': 0,
62
+ ...tsEslintPlugin.configs.recommended.rules,
63
+ ...tsEslintPlugin.configs['recommended-requiring-type-checking'].rules,
64
+ ...tsImportPlugin.configs.errors.rules,
65
+ ...tsImportPlugin.configs.warnings.rules,
66
+ ...tsImportPlugin.configs.typescript.rules,
67
+
68
+ 'import/no-unresolved': 'error',
69
+ 'import/extensions': ['error', 'ignorePackages', { ts: 'never' }],
70
+
71
+ '@typescript-eslint/await-thenable': 'warn',
72
+ '@typescript-eslint/brace-style': 'off',
73
+ /*
56
74
  '@typescript-eslint/comma-dangle': [
57
75
  2,
58
76
  {
@@ -66,6 +84,7 @@ module.exports = {
66
84
  tuples: 'always-multiline',
67
85
  },
68
86
  ],
87
+ */
69
88
  '@typescript-eslint/explicit-function-return-type': [
70
89
  'error',
71
90
  {
@@ -73,7 +92,7 @@ module.exports = {
73
92
  },
74
93
  ],
75
94
  '@typescript-eslint/explicit-member-accessibility': [
76
- 2,
95
+ 'error',
77
96
  {
78
97
  accessibility: 'explicit',
79
98
  overrides: {
@@ -81,8 +100,8 @@ module.exports = {
81
100
  },
82
101
  },
83
102
  ],
84
- '@typescript-eslint/explicit-module-boundary-types': 2,
85
- '@typescript-eslint/indent': 0,
103
+ '@typescript-eslint/explicit-module-boundary-types': 'error',
104
+ '@typescript-eslint/indent': 'off',
86
105
  '@typescript-eslint/member-ordering': [
87
106
  'error',
88
107
  {
@@ -116,7 +135,7 @@ module.exports = {
116
135
  },
117
136
  ],
118
137
  '@typescript-eslint/naming-convention': [
119
- 2,
138
+ 'error',
120
139
  {
121
140
  format: ['camelCase'],
122
141
  leadingUnderscore: 'forbid',
@@ -178,21 +197,10 @@ module.exports = {
178
197
  selector: 'enumMember',
179
198
  trailingUnderscore: 'forbid',
180
199
  },
181
- // Правило, разрешающее написание "_id" с нижним подчеркиванием
182
- {
183
- filter: {
184
- match: true,
185
- regex: '^_id$',
186
- },
187
- format: null,
188
- leadingUnderscore: 'allow',
189
- selector: 'typeProperty',
190
- trailingUnderscore: 'forbid',
191
- },
192
200
  ],
193
- '@typescript-eslint/no-base-to-string': 0,
201
+ '@typescript-eslint/no-base-to-string': 'off',
194
202
  '@typescript-eslint/no-empty-function': [
195
- 2,
203
+ 'error',
196
204
  {
197
205
  allow: [
198
206
  'constructors',
@@ -201,13 +209,13 @@ module.exports = {
201
209
  'decoratedFunctions',
202
210
  'overrideMethods',
203
211
  'setters',
204
- ]
205
- }
212
+ ],
213
+ },
206
214
  ],
207
- '@typescript-eslint/no-explicit-any': 2,
208
- '@typescript-eslint/no-floating-promises': 0,
215
+ '@typescript-eslint/no-explicit-any': 'error',
216
+ '@typescript-eslint/no-floating-promises': 'off',
209
217
  '@typescript-eslint/no-magic-numbers': [
210
- 1,
218
+ 'warn',
211
219
  {
212
220
  ignore: [-1, 0, 1],
213
221
  ignoreArrayIndexes: true,
@@ -217,15 +225,15 @@ module.exports = {
217
225
  ignoreReadonlyClassProperties: true,
218
226
  },
219
227
  ],
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,
228
+ '@typescript-eslint/no-misused-promises': 'off',
229
+ '@typescript-eslint/no-unsafe-argument': 'off',
230
+ '@typescript-eslint/no-unsafe-assignment': 'off',
231
+ '@typescript-eslint/no-unsafe-call': 'off',
232
+ '@typescript-eslint/no-unsafe-member-access': 'off',
233
+ '@typescript-eslint/no-unsafe-return': 'off',
234
+ '@typescript-eslint/require-await': 'warn',
227
235
  '@typescript-eslint/restrict-template-expressions': [
228
- 2,
236
+ 'error',
229
237
  {
230
238
  allowAny: true,
231
239
  allowBoolean: true,
@@ -233,25 +241,25 @@ module.exports = {
233
241
  allowNumber: true,
234
242
  },
235
243
  ],
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
+ '@typescript-eslint/return-await': ['error', 'in-try-catch'],
245
+ 'import/default': 'error',
246
+ 'import/export': 'error',
247
+ 'import/first': 'error',
248
+ 'import/named': 'error',
249
+ 'import/namespace': 'error',
250
+ 'import/newline-after-import': 'error',
251
+ 'import/no-cycle': 'error',
244
252
  'import/no-extraneous-dependencies': [
245
- 2,
253
+ 'error',
246
254
  {
247
255
  devDependencies: true,
248
256
  },
249
257
  ],
250
- 'import/order': 0,
251
- 'import/prefer-default-export': 0,
252
- 'max-classes-per-file': 0,
258
+ 'import/order': 'off',
259
+ 'import/prefer-default-export': 'off',
260
+ 'max-classes-per-file': 'off',
253
261
  'simple-import-sort/imports': [
254
- 2,
262
+ 'error',
255
263
  {
256
264
  groups: [
257
265
  // Side effect imports.
@@ -272,9 +280,9 @@ module.exports = {
272
280
  ],
273
281
  },
274
282
  ],
275
- 'sort-imports': 0,
276
- 'typescript-sort/interface': 2,
277
- 'typescript-sort/type': 2,
278
- 'typescript-sort/enum': 2,
283
+ 'sort-imports': 'off',
284
+ 'typescript-sort/interface': 'error',
285
+ 'typescript-sort/type': 'error',
286
+ 'typescript-sort/enum': 'error',
279
287
  },
280
- };
288
+ });
package/eslint/node.js CHANGED
@@ -1,28 +1,26 @@
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
+ files: files || ['**/*.js', '**/*.ts'],
7
+ plugins: {
8
+ jest: jestPlugin,
9
+ prettier: prettierPlugin,
8
10
  },
9
- extends: [
10
- 'eslint:recommended',
11
- 'plugin:jest/recommended',
12
- 'plugin:jest/style',
13
- 'prettier',
14
- 'plugin:prettier/recommended',
15
- ],
16
- plugins: ['jest'],
17
11
  rules: {
12
+ ...jestPlugin.configs.recommended.rules,
13
+ ...jestPlugin.configs.style.rules,
14
+ ...prettierPlugin.configs.recommended.rules,
15
+
18
16
  'arrow-parens': [
19
- 1,
17
+ 'warn',
20
18
  'always',
21
19
  {
22
20
  requireForBlockBody: true,
23
21
  },
24
22
  ],
25
- 'class-methods-use-this': 0,
23
+ 'class-methods-use-this': 'off',
26
24
  'comma-dangle': [
27
25
  'error',
28
26
  {
@@ -33,13 +31,14 @@ module.exports = {
33
31
  objects: 'always-multiline',
34
32
  },
35
33
  ],
36
- complexity: [2, { max: 10 }],
37
- 'function-paren-newline': 0,
38
- 'implicit-arrow-linebreak': 0,
39
- 'import/extensions': 0,
40
- 'import/no-unresolved': 0,
34
+ complexity: ['error', { max: 10 }],
35
+ 'function-paren-newline': 'off',
36
+ 'implicit-arrow-linebreak': 'off',
37
+ 'import/extensions': 'off',
38
+ 'import/no-unresolved': 'off',
39
+ 'jest/no-deprecated-functions': isModuleInstalled('jest') ? 'error' : 'off',
41
40
  'max-lines': [
42
- 2,
41
+ 'error',
43
42
  {
44
43
  max: 500,
45
44
  skipBlankLines: false,
@@ -47,27 +46,27 @@ module.exports = {
47
46
  },
48
47
  ],
49
48
  'max-lines-per-function': [
50
- 2,
49
+ 'error',
51
50
  {
52
51
  max: 200,
53
52
  skipBlankLines: false,
54
53
  skipComments: true,
55
54
  },
56
55
  ],
57
- 'max-params': [2, { max: 3 }],
58
- 'no-await-in-loop': 0,
59
- 'no-continue': 0,
60
- 'no-empty-function': 0,
56
+ 'max-params': ['error', { max: 3 }],
57
+ 'no-await-in-loop': 'off',
58
+ 'no-continue': 'off',
59
+ 'no-empty-function': 'off',
61
60
  'no-plusplus': [
62
- 2,
61
+ 'error',
63
62
  {
64
63
  allowForLoopAfterthoughts: true,
65
64
  },
66
65
  ],
67
- 'no-promise-executor-return': 0,
68
- 'no-restricted-syntax': 0,
66
+ 'no-promise-executor-return': 'off',
67
+ 'no-restricted-syntax': 'off',
69
68
  'padding-line-between-statements': [
70
- 2,
69
+ 'error',
71
70
  {
72
71
  blankLine: 'always',
73
72
  next: '*',
@@ -84,8 +83,10 @@ module.exports = {
84
83
  prev: 'if',
85
84
  },
86
85
  ],
87
- 'require-await': 0,
88
- 'sort-imports': 2,
89
- 'sort-keys': 2,
86
+ 'prettier/prettier': 'error',
87
+ 'require-await': 'off',
88
+ 'sort-imports': 'error',
89
+ 'sort-keys': 'error',
90
+ strict: 'off',
90
91
  },
91
- };
92
+ });
@@ -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.typeParameters?.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.2",
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');