eslint-config-angular-strict 2.3.82 → 2.3.84

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
@@ -24,6 +24,7 @@ A production-ready, opinionated ESLint configuration that enforces best practice
24
24
  📘 **TypeScript**: Promise-async, type imports, strict typing, type safety, extraneous classes,...<br>
25
25
  ✨ **Code Quality**: Complexity max, file length control, import cycles detection, 100+ Unicorn best practices,...<br>
26
26
  🎨 **Style**: Airbnb extended, max line length, object/class newlines, sorted classes/imports/objects/types,...<br>
27
+ 📁 **Naming**: File/folder/class suffix conventions enforced (components, services, pipes, interfaces,...)<br>
27
28
  🔍 **Templates**: 30+ rules with alphabetical attrs, complexity max, control flow, trackBy, a11y, no-any,...
28
29
 
29
30
  ## What's Included
package/index.js CHANGED
@@ -9,6 +9,8 @@ import tsEslintParser from '@typescript-eslint/parser';
9
9
  import tsEslintPlugin from '@typescript-eslint/eslint-plugin';
10
10
  import unicornPlugin from 'eslint-plugin-unicorn';
11
11
 
12
+ import { namingConventionOverrides, noRestrictedSyntaxRule } from './naming-conventions.js';
13
+
12
14
  export default [
13
15
  {
14
16
  ignores: ['dist/**', 'node_modules/**'],
@@ -47,12 +49,7 @@ export default [
47
49
  'no-fallthrough': 'off',
48
50
  'no-param-reassign': ['error', { props: false }],
49
51
  'no-plusplus': 'off',
50
- 'no-restricted-syntax': [
51
- 'error',
52
- { selector: 'TSEnumDeclaration:not(TSModuleBlock > TSEnumDeclaration)', message: 'Enums must live in enums.ts or *.enum.ts files' },
53
- { selector: 'TSInterfaceDeclaration:not(TSModuleBlock > TSInterfaceDeclaration)', message: 'Interfaces must live in *.interface.ts files' },
54
- { selector: 'TSTypeAliasDeclaration:not(TSModuleBlock > TSTypeAliasDeclaration)', message: 'Types must live in types.ts or *.type.ts files' },
55
- ],
52
+ 'no-restricted-syntax': noRestrictedSyntaxRule,
56
53
  'no-return-assign': 'off',
57
54
  'no-underscore-dangle': ['error', { allowAfterThis: true }],
58
55
  'radix': ['error', 'as-needed'],
@@ -301,45 +298,5 @@ export default [
301
298
  },
302
299
  },
303
300
 
304
- // *.interface.ts: only interfaces allowed
305
- {
306
- files: ['**/*.interface.ts'],
307
- rules: {
308
- 'no-restricted-syntax': [
309
- 'error',
310
- {
311
- selector: 'Program > :not(ImportDeclaration, TSInterfaceDeclaration, ExportNamedDeclaration:has(> TSInterfaceDeclaration))',
312
- message: 'Only interfaces allowed in *.interface.ts',
313
- },
314
- ],
315
- },
316
- },
317
-
318
- // enums.ts / *.enum.ts: only enums allowed
319
- {
320
- files: ['**/enums.ts', '**/*.enum.ts'],
321
- rules: {
322
- 'no-restricted-syntax': [
323
- 'error',
324
- {
325
- selector: 'Program > :not(ImportDeclaration, TSEnumDeclaration, ExportNamedDeclaration:has(> TSEnumDeclaration))',
326
- message: 'Only enums allowed in enums.ts / *.enum.ts',
327
- },
328
- ],
329
- },
330
- },
331
-
332
- // types.ts / *.type.ts: only type aliases allowed
333
- {
334
- files: ['**/types.ts', '**/*.type.ts'],
335
- rules: {
336
- 'no-restricted-syntax': [
337
- 'error',
338
- {
339
- selector: 'Program > :not(ImportDeclaration, TSTypeAliasDeclaration, ExportNamedDeclaration:has(> TSTypeAliasDeclaration))',
340
- message: 'Only type aliases allowed in types.ts / *.type.ts',
341
- },
342
- ],
343
- },
344
- },
301
+ ...namingConventionOverrides,
345
302
  ];
@@ -0,0 +1,245 @@
1
+ // Class suffix enforcement (regex matched against class name)
2
+ const classSuffixRestriction = (suffix, location) => ({
3
+ selector: `ClassDeclaration[id.name!=/${suffix}$/]`,
4
+ message: `Classes in ${location} must end with "${suffix}"`,
5
+ });
6
+
7
+ // Files containing only imports + export const (literal values, not functions) should be named *.constant.ts
8
+ const constantsFileRestriction = {
9
+ selector:
10
+ 'Program:has(ExportNamedDeclaration VariableDeclaration[kind="const"]):not(:has(:matches(ClassDeclaration, FunctionDeclaration, TSInterfaceDeclaration, TSEnumDeclaration, TSTypeAliasDeclaration))):not(:has(VariableDeclarator > :matches(ArrowFunctionExpression, FunctionExpression, CallExpression)))',
11
+ message: 'Files containing only constants must be named constants/*.constant.ts',
12
+ };
13
+
14
+ // Restrictions for interface/enum/type declarations (skipped inside `declare module` blocks)
15
+ const declarationFileRestrictions = [
16
+ { selector: 'TSEnumDeclaration:not(TSModuleBlock > TSEnumDeclaration)', message: 'Enums must live in interfaces/enums.ts or interfaces/*.enum.ts files' },
17
+ { selector: 'TSInterfaceDeclaration:not(TSModuleBlock > TSInterfaceDeclaration)', message: 'Interfaces must live in interfaces/*.interface.ts files' },
18
+ {
19
+ selector: 'TSTypeAliasDeclaration:not(TSModuleBlock > TSTypeAliasDeclaration)',
20
+ message: 'Types must live in interfaces/types.ts or interfaces/*.type.ts files',
21
+ },
22
+ ];
23
+
24
+ // Restrictions for Angular class decorators (one decorator type per file)
25
+ const decoratorFileRestrictions = {
26
+ Component: {
27
+ selector: 'Decorator[expression.callee.name="Component"]',
28
+ message:
29
+ '@Component classes must live in app.ts / components/**/*.component.ts / components/drawers/*.drawer.ts / components/modals/*.modal.ts / pages/**/*.page.ts',
30
+ },
31
+ Directive: { selector: 'Decorator[expression.callee.name="Directive"]', message: '@Directive classes must live in directives/*.directive.ts' },
32
+ Injectable: { selector: 'Decorator[expression.callee.name="Injectable"]', message: '@Injectable classes must live in services/*.service.ts' },
33
+ Pipe: { selector: 'Decorator[expression.callee.name="Pipe"]', message: '@Pipe classes must live in pipes/*.pipe.ts' },
34
+ };
35
+
36
+ // Undecorated classes must live in helpers/*.helpers.ts
37
+ const helperFileRestriction = {
38
+ selector: 'ClassDeclaration:not(:has(Decorator))',
39
+ message: 'Undecorated classes must live in helpers/*.helpers.ts',
40
+ };
41
+
42
+ // Global no-restricted-syntax rule (used in the main TypeScript files block)
43
+ export const noRestrictedSyntaxRule = [
44
+ 'error',
45
+ constantsFileRestriction,
46
+ decoratorFileRestrictions.Component,
47
+ decoratorFileRestrictions.Directive,
48
+ decoratorFileRestrictions.Injectable,
49
+ decoratorFileRestrictions.Pipe,
50
+ helperFileRestriction,
51
+ ...declarationFileRestrictions,
52
+ ];
53
+
54
+ // Per-file overrides relaxing no-restricted-syntax to allow the relevant decorator/declaration
55
+ export const namingConventionOverrides = [
56
+ // app.ts / components/**/*.component.ts / components/drawers/*.drawer.ts / components/modals/*.modal.ts / pages/**/*.page.ts: @Component allowed
57
+ {
58
+ files: ['**/app.ts', '**/components/**/*.component.ts', '**/components/drawers/**/*.drawer.ts', '**/components/modals/**/*.modal.ts', '**/pages/**/*.page.ts'],
59
+ rules: {
60
+ 'no-restricted-syntax': [
61
+ 'error',
62
+ decoratorFileRestrictions.Directive,
63
+ decoratorFileRestrictions.Injectable,
64
+ decoratorFileRestrictions.Pipe,
65
+ helperFileRestriction,
66
+ ...declarationFileRestrictions,
67
+ ],
68
+ },
69
+ },
70
+
71
+ // directives/*.directive.ts: @Directive allowed
72
+ {
73
+ files: ['**/directives/**/*.directive.ts'],
74
+ rules: {
75
+ 'no-restricted-syntax': [
76
+ 'error',
77
+ decoratorFileRestrictions.Component,
78
+ decoratorFileRestrictions.Injectable,
79
+ decoratorFileRestrictions.Pipe,
80
+ helperFileRestriction,
81
+ ...declarationFileRestrictions,
82
+ ],
83
+ },
84
+ },
85
+
86
+ // pipes/*.pipe.ts: @Pipe allowed, class must end with "Pipe"
87
+ {
88
+ files: ['**/pipes/**/*.pipe.ts'],
89
+ rules: {
90
+ 'no-restricted-syntax': [
91
+ 'error',
92
+ classSuffixRestriction('Pipe', '*.pipe.ts'),
93
+ decoratorFileRestrictions.Component,
94
+ decoratorFileRestrictions.Directive,
95
+ decoratorFileRestrictions.Injectable,
96
+ helperFileRestriction,
97
+ ...declarationFileRestrictions,
98
+ ],
99
+ },
100
+ },
101
+
102
+ // services/*.service.ts: @Injectable allowed, class must end with "Service"
103
+ {
104
+ files: ['**/services/**/*.service.ts'],
105
+ rules: {
106
+ 'no-restricted-syntax': [
107
+ 'error',
108
+ classSuffixRestriction('Service', '*.service.ts'),
109
+ decoratorFileRestrictions.Component,
110
+ decoratorFileRestrictions.Directive,
111
+ decoratorFileRestrictions.Pipe,
112
+ helperFileRestriction,
113
+ ...declarationFileRestrictions,
114
+ ],
115
+ },
116
+ },
117
+
118
+ // helpers/*.helpers.ts: undecorated classes allowed, class must end with "Helpers"
119
+ {
120
+ files: ['**/helpers/**/*.helpers.ts'],
121
+ rules: {
122
+ 'no-restricted-syntax': [
123
+ 'error',
124
+ classSuffixRestriction('Helpers', '*.helpers.ts'),
125
+ decoratorFileRestrictions.Component,
126
+ decoratorFileRestrictions.Directive,
127
+ decoratorFileRestrictions.Injectable,
128
+ decoratorFileRestrictions.Pipe,
129
+ ...declarationFileRestrictions,
130
+ ],
131
+ },
132
+ },
133
+
134
+ // interfaces/*.interface.ts: only interfaces allowed
135
+ {
136
+ files: ['**/interfaces/**/*.interface.ts'],
137
+ rules: {
138
+ 'no-restricted-syntax': [
139
+ 'error',
140
+ {
141
+ selector: 'Program > :not(ImportDeclaration, TSInterfaceDeclaration, ExportNamedDeclaration:has(> TSInterfaceDeclaration))',
142
+ message: 'Only interfaces allowed in interfaces/*.interface.ts',
143
+ },
144
+ ],
145
+ },
146
+ },
147
+
148
+ // interfaces/enums.ts / interfaces/*.enum.ts: only enums allowed
149
+ {
150
+ files: ['**/interfaces/enums.ts', '**/interfaces/**/*.enum.ts'],
151
+ rules: {
152
+ 'no-restricted-syntax': [
153
+ 'error',
154
+ {
155
+ selector: 'Program > :not(ImportDeclaration, TSEnumDeclaration, ExportNamedDeclaration:has(> TSEnumDeclaration))',
156
+ message: 'Only enums allowed in interfaces/enums.ts / interfaces/*.enum.ts',
157
+ },
158
+ ],
159
+ },
160
+ },
161
+
162
+ // interfaces/types.ts / interfaces/*.type.ts: only type aliases allowed
163
+ {
164
+ files: ['**/interfaces/types.ts', '**/interfaces/**/*.type.ts'],
165
+ rules: {
166
+ 'no-restricted-syntax': [
167
+ 'error',
168
+ {
169
+ selector: 'Program > :not(ImportDeclaration, TSTypeAliasDeclaration, ExportNamedDeclaration:has(> TSTypeAliasDeclaration))',
170
+ message: 'Only type aliases allowed in interfaces/types.ts / interfaces/*.type.ts',
171
+ },
172
+ ],
173
+ },
174
+ },
175
+
176
+ // constants/*.constant.ts: only imports + export const allowed
177
+ {
178
+ files: ['**/constants/**/*.constant.ts'],
179
+ rules: {
180
+ 'no-restricted-syntax': [
181
+ 'error',
182
+ {
183
+ selector: 'Program > :not(ImportDeclaration, ExportNamedDeclaration:has(> VariableDeclaration[kind="const"]))',
184
+ message: 'Only imports and export const allowed in constants/*.constant.ts',
185
+ },
186
+ ],
187
+ },
188
+ },
189
+
190
+ // guards/*.guard.ts: only imports + export const allowed (functional guards)
191
+ {
192
+ files: ['**/guards/**/*.guard.ts'],
193
+ rules: {
194
+ 'no-restricted-syntax': [
195
+ 'error',
196
+ {
197
+ selector: 'Program > :not(ImportDeclaration, ExportNamedDeclaration:has(> VariableDeclaration[kind="const"]))',
198
+ message: 'Only imports and export const allowed in guards/*.guard.ts',
199
+ },
200
+ ],
201
+ },
202
+ },
203
+
204
+ // interceptors/*.interceptor.ts: only imports + export const allowed (functional interceptors)
205
+ {
206
+ files: ['**/interceptors/**/*.interceptor.ts'],
207
+ rules: {
208
+ 'no-restricted-syntax': [
209
+ 'error',
210
+ {
211
+ selector: 'Program > :not(ImportDeclaration, ExportNamedDeclaration:has(> VariableDeclaration[kind="const"]))',
212
+ message: 'Only imports and export const allowed in interceptors/*.interceptor.ts',
213
+ },
214
+ ],
215
+ },
216
+ },
217
+
218
+ // resolvers/*.resolver.ts: only imports + export const allowed (functional resolvers)
219
+ {
220
+ files: ['**/resolvers/**/*.resolver.ts'],
221
+ rules: {
222
+ 'no-restricted-syntax': [
223
+ 'error',
224
+ {
225
+ selector: 'Program > :not(ImportDeclaration, ExportNamedDeclaration:has(> VariableDeclaration[kind="const"]))',
226
+ message: 'Only imports and export const allowed in resolvers/*.resolver.ts',
227
+ },
228
+ ],
229
+ },
230
+ },
231
+
232
+ // *.routes.ts: only imports + export const allowed (route definitions, any folder)
233
+ {
234
+ files: ['**/*.routes.ts'],
235
+ rules: {
236
+ 'no-restricted-syntax': [
237
+ 'error',
238
+ {
239
+ selector: 'Program > :not(ImportDeclaration, ExportNamedDeclaration:has(> VariableDeclaration[kind="const"]))',
240
+ message: 'Only imports and export const allowed in *.routes.ts',
241
+ },
242
+ ],
243
+ },
244
+ },
245
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-config-angular-strict",
3
- "version": "2.3.82",
3
+ "version": "2.3.84",
4
4
  "description": "Modern ESLint configuration with strict rules for Angular development.",
5
5
  "keywords": [
6
6
  "angular",
@@ -40,10 +40,10 @@
40
40
  "@angular-eslint/eslint-plugin-template": "21.3.1",
41
41
  "@angular-eslint/template-parser": "21.3.1",
42
42
  "@stylistic/eslint-plugin": "5.10.0",
43
- "@typescript-eslint/eslint-plugin": "8.59.2",
44
- "@typescript-eslint/parser": "8.59.2",
45
- "@typescript-eslint/types": "8.59.2",
46
- "@typescript-eslint/utils": "8.59.2",
43
+ "@typescript-eslint/eslint-plugin": "8.59.3",
44
+ "@typescript-eslint/parser": "8.59.3",
45
+ "@typescript-eslint/types": "8.59.3",
46
+ "@typescript-eslint/utils": "8.59.3",
47
47
  "eslint": "9.39.4",
48
48
  "eslint-config-airbnb-extended": "3.1.0",
49
49
  "eslint-plugin-import-x": "4.16.2",