@ethlete/eslint-plugin 0.1.0-beta.5

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +25 -0
  3. package/package.json +14 -0
  4. package/src/configs/recommended.js +403 -0
  5. package/src/index.js +136 -0
  6. package/src/rules/angular-decorator-property-order.js +205 -0
  7. package/src/rules/class-constant-property.js +344 -0
  8. package/src/rules/class-member-order.js +431 -0
  9. package/src/rules/enforce-routing-view-naming.js +84 -0
  10. package/src/rules/guard-return-newline.js +81 -0
  11. package/src/rules/inject-member-accessibility.js +93 -0
  12. package/src/rules/internals/angular-member-visibility.js +233 -0
  13. package/src/rules/internals/implemented-contract-members.js +294 -0
  14. package/src/rules/no-angular-router-api.js +158 -0
  15. package/src/rules/no-angular-seo-services.js +82 -0
  16. package/src/rules/no-direct-dom-manipulation.js +160 -0
  17. package/src/rules/no-document-cookie.js +55 -0
  18. package/src/rules/no-dom-query.js +81 -0
  19. package/src/rules/no-empty-angular-metadata-arrays.js +104 -0
  20. package/src/rules/no-empty-newlines-between-imports.js +45 -0
  21. package/src/rules/no-inject-chain.js +44 -0
  22. package/src/rules/no-leading-underscore-class-member.js +120 -0
  23. package/src/rules/no-legacy-angular-decorators.js +405 -0
  24. package/src/rules/no-locale-id.js +57 -0
  25. package/src/rules/no-member-alias.js +151 -0
  26. package/src/rules/no-native-observers.js +102 -0
  27. package/src/rules/no-pipe-logic.js +76 -0
  28. package/src/rules/no-public-property.js +85 -0
  29. package/src/rules/no-readonly-signal.js +119 -0
  30. package/src/rules/no-redundant-internal.js +94 -0
  31. package/src/rules/no-rxjs-in-effect.js +88 -0
  32. package/src/rules/no-screaming-case-local.js +113 -0
  33. package/src/rules/no-standalone-flag.js +95 -0
  34. package/src/rules/no-subscribe-in-pipe.js +89 -0
  35. package/src/rules/no-subscribe-with-body.js +95 -0
  36. package/src/rules/no-trivial-return-type.js +104 -0
  37. package/src/rules/no-trivial-wrapper-method.js +120 -0
  38. package/src/rules/no-type-only-import.js +79 -0
  39. package/src/rules/no-typed-injected-element-ref.js +88 -0
  40. package/src/rules/no-unused-class-member.js +209 -0
  41. package/src/rules/no-window-location.js +109 -0
  42. package/src/rules/prefer-clone-equal.js +112 -0
  43. package/src/rules/prefer-concise-angular-host-directives.js +225 -0
  44. package/src/rules/prefer-concise-angular-style-metadata.js +108 -0
  45. package/src/rules/prefer-element-dimensions.js +128 -0
  46. package/src/rules/prefer-linked-signal.js +124 -0
  47. package/src/rules/prefer-match-media.js +101 -0
  48. package/src/rules/prefer-rxjs-timer.js +82 -0
  49. package/src/rules/prefer-scroll-state.js +158 -0
  50. package/src/rules/prefer-viewport-size.js +75 -0
  51. package/src/rules/require-dollar-suffix.js +123 -0
  52. package/src/rules/require-on-push-change-detection.js +271 -0
  53. package/src/rules/require-view-encapsulation-none.js +264 -0
  54. package/src/rules/template-member-accessibility.js +190 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,39 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0-beta.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [`ddb5d09`](https://github.com/ethlete-io/ethdk/commit/ddb5d09e4bc56e18cc8c228aa78a200441e7a766) Thanks [@TomTomB](https://github.com/TomTomB)! - Bump to beta
8
+
9
+ ## 0.1.0-next.4
10
+
11
+ ### Patch Changes
12
+
13
+ - [`a690217`](https://github.com/ethlete-io/ethdk/commit/a6902172efd9bd1956a16237e79acbfbd816d946) Thanks [@TomTomB](https://github.com/TomTomB)! - Version bump only
14
+
15
+ ## 0.1.0-next.3
16
+
17
+ ### Patch Changes
18
+
19
+ - [`5d1d3ac`](https://github.com/ethlete-io/ethdk/commit/5d1d3accbd4a7657bc50cdc0653a7ca24fe761e2) Thanks [@TomTomB](https://github.com/TomTomB)! - Fix minor lint edge cases
20
+
21
+ ## 0.1.0-next.2
22
+
23
+ ### Patch Changes
24
+
25
+ - [#2957](https://github.com/ethlete-io/ethdk/pull/2957) [`81173d4`](https://github.com/ethlete-io/ethdk/commit/81173d426d1a53ea2a7af0d16d6eb0fe4a16dbd9) Thanks [@github-actions](https://github.com/apps/github-actions)! - Add missing auto fixers
26
+
27
+ ## 0.1.0-next.1
28
+
29
+ ### Patch Changes
30
+
31
+ - [`6ec51b4`](https://github.com/ethlete-io/ethdk/commit/6ec51b49497ba53bf34ec0aabcf519957ba0e412) Thanks [@TomTomB](https://github.com/TomTomB)! - Fix plugin not including js files
32
+
33
+ ## 0.1.0-next.0
34
+
35
+ ### Minor Changes
36
+
37
+ - [#2933](https://github.com/ethlete-io/ethdk/pull/2933) [`3f22771`](https://github.com/ethlete-io/ethdk/commit/3f22771bb3a339461ce8c70a48e573897579f3c4) Thanks [@github-actions](https://github.com/apps/github-actions)! - Add basic set of styleguide based rules
38
+
39
+ All notable changes to this project will be documented in this file.
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # @ethlete/eslint-plugin
2
+
3
+ ESLint plugin with custom rules and shareable flat configs for the Ethlete coding styleguide.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install --save-dev @ethlete/eslint-plugin
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```js
14
+ // eslint.config.mjs
15
+ import ethlete from '@ethlete/eslint-plugin';
16
+
17
+ export default [...ethlete.configs.recommended];
18
+ ```
19
+
20
+ ## Rules
21
+
22
+ | Rule | Description |
23
+ | -------------------------------- | ----------------------------------------------------------------------------------------------- |
24
+ | `ethlete/no-inject-chain` | Disallows `inject(X).member` chaining — use a `const` instead |
25
+ | `ethlete/no-trivial-return-type` | Disallows explicit `void`, `boolean`, `string`, `number` return types that TypeScript can infer |
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "@ethlete/eslint-plugin",
3
+ "version": "0.1.0-beta.5",
4
+ "license": "MIT",
5
+ "type": "commonjs",
6
+ "main": "./src/index.js",
7
+ "peerDependencies": {
8
+ "@angular-eslint/eslint-plugin-template": ">=21.0.0",
9
+ "@typescript-eslint/eslint-plugin": ">=8.0.0",
10
+ "eslint": ">=9.0.0",
11
+ "tslib": "2.8.1",
12
+ "typescript": "5.9.3"
13
+ }
14
+ }
@@ -0,0 +1,403 @@
1
+ // @ts-check
2
+ 'use strict';
3
+
4
+ /**
5
+ * Flat config entries for TypeScript files.
6
+ * Covers all lintable rules from the Ethlete styleguide.
7
+ *
8
+ * @type {import('eslint').Linter.Config}
9
+ */
10
+ const recommendedTs = {
11
+ files: ['**/*.ts'],
12
+ plugins: {
13
+ // The plugin itself is injected by the caller (see index.js)
14
+ },
15
+ rules: {
16
+ // ── TypeScript ──────────────────────────────────────────────────────────
17
+
18
+ // No interface — use type
19
+ '@typescript-eslint/consistent-type-definitions': ['error', 'type'],
20
+
21
+ // No any
22
+ '@typescript-eslint/no-explicit-any': 'error',
23
+
24
+ // No declared-but-never-read variables, parameters, or imports
25
+ // Args prefixed with _ are exempt (intentionally unused callback params)
26
+ 'no-unused-vars': 'off', // disabled in favour of the TS-aware version below
27
+ '@typescript-eslint/no-unused-vars': [
28
+ 'error',
29
+ {
30
+ vars: 'all',
31
+ args: 'all',
32
+ argsIgnorePattern: '^_',
33
+ caughtErrors: 'all',
34
+ caughtErrorsIgnorePattern: '^_',
35
+ ignoreRestSiblings: true,
36
+ },
37
+ ],
38
+
39
+ // ── Naming & formatting ─────────────────────────────────────────────────
40
+
41
+ // No var
42
+ 'no-var': 'error',
43
+
44
+ // No unnecessary let — default to const
45
+ 'prefer-const': 'error',
46
+
47
+ // Never declare multiple variables in one statement
48
+ 'one-var': ['error', 'never'],
49
+
50
+ // Always === / !==
51
+ eqeqeq: ['error', 'always'],
52
+
53
+ // Max two function parameters
54
+ 'max-params': ['error', 2],
55
+
56
+ // No _ or # prefixes; enforce camelCase / PascalCase / UPPER_CASE
57
+ '@typescript-eslint/naming-convention': [
58
+ 'error',
59
+ {
60
+ selector: 'default',
61
+ leadingUnderscore: 'forbid',
62
+ trailingUnderscore: 'forbid',
63
+ format: null,
64
+ },
65
+ // Allow any format for object literal properties that require quotes
66
+ // (e.g. Angular host binding keys: '[class.foo]', '(click)', '[attr.aria-label]')
67
+ {
68
+ selector: 'objectLiteralProperty',
69
+ modifiers: ['requiresQuotes'],
70
+ format: null,
71
+ },
72
+ {
73
+ selector: ['variable', 'parameter', 'property', 'parameterProperty', 'accessor'],
74
+ leadingUnderscore: 'allow',
75
+ trailingUnderscore: 'forbid',
76
+ format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
77
+ },
78
+ {
79
+ selector: 'method',
80
+ leadingUnderscore: 'allow',
81
+ trailingUnderscore: 'forbid',
82
+ format: null,
83
+ },
84
+ {
85
+ selector: 'typeLike',
86
+ leadingUnderscore: 'forbid',
87
+ trailingUnderscore: 'forbid',
88
+ format: ['PascalCase'],
89
+ },
90
+ // Generic type parameters must be T-prefixed: TValue, TResult — never bare T
91
+ {
92
+ selector: 'typeParameter',
93
+ leadingUnderscore: 'forbid',
94
+ trailingUnderscore: 'forbid',
95
+ prefix: ['T'],
96
+ format: ['PascalCase'],
97
+ },
98
+ {
99
+ selector: 'enumMember',
100
+ leadingUnderscore: 'forbid',
101
+ trailingUnderscore: 'forbid',
102
+ format: ['UPPER_CASE', 'PascalCase'],
103
+ },
104
+ ],
105
+
106
+ // ── Banned syntax ───────────────────────────────────────────────────────
107
+
108
+ 'no-restricted-syntax': [
109
+ 'error',
110
+ // No enum — use const object + derived union type
111
+ {
112
+ selector: 'TSEnumDeclaration',
113
+ message: 'No enums. Use a const object + derived union type instead.',
114
+ },
115
+ // No standalone function declarations / expressions — use arrow functions
116
+ {
117
+ selector: 'FunctionDeclaration',
118
+ message: 'No function declarations. Use arrow functions instead.',
119
+ },
120
+ {
121
+ selector: "FunctionExpression:not([parent.type='Property']):not([parent.type='MethodDefinition'])",
122
+ message: 'No function expressions outside of object methods or class methods. Use arrow functions.',
123
+ },
124
+ // No arrow properties in classes — use regular methods
125
+ {
126
+ selector: 'PropertyDefinition > ArrowFunctionExpression',
127
+ message: 'No arrow function properties in classes. Use regular methods.',
128
+ },
129
+ // No async/await — use RxJS
130
+ {
131
+ selector: 'FunctionDeclaration[async=true]',
132
+ message: 'No async/await. Use RxJS for all async operations.',
133
+ },
134
+ {
135
+ selector: 'ArrowFunctionExpression[async=true]',
136
+ message: 'No async/await. Use RxJS for all async operations.',
137
+ },
138
+ {
139
+ selector: 'FunctionExpression[async=true]',
140
+ message: 'No async/await. Use RxJS for all async operations.',
141
+ },
142
+ // No static class members
143
+ {
144
+ selector: 'MethodDefinition[static=true]',
145
+ message: 'No static class members.',
146
+ },
147
+ {
148
+ selector: 'PropertyDefinition[static=true]',
149
+ message: 'No static class members.',
150
+ },
151
+ // No # prefix — use private keyword
152
+ {
153
+ selector: "PropertyDefinition[key.type='PrivateIdentifier']",
154
+ message: 'No # prefix on class members. Use the private keyword instead.',
155
+ },
156
+ {
157
+ selector: "MethodDefinition[key.type='PrivateIdentifier']",
158
+ message: 'No # prefix on class members. Use the private keyword instead.',
159
+ },
160
+ // No constructor injection — use inject()
161
+ {
162
+ selector: 'TSParameterProperty[accessibility]',
163
+ message: 'No constructor injection. Use inject() instead.',
164
+ },
165
+ // No legacy Angular lifecycle hooks
166
+ {
167
+ selector:
168
+ 'MethodDefinition[key.name=/^ngOnChanges$|^ngAfterViewInit$|^ngAfterContentInit$|^ngAfterContentChecked$|^ngAfterViewChecked$|^ngDoCheck$/]',
169
+ message:
170
+ 'No legacy Angular lifecycle hooks. Use afterNextRender, inject(DestroyRef).onDestroy, or constructor-based effects instead.',
171
+ },
172
+ // No @Injectable — use createRootProvider / createProvider from @ethlete/core
173
+ {
174
+ selector: "Decorator[expression.callee.name='Injectable'], Decorator[expression.name='Injectable']",
175
+ message: 'No @Injectable services. Use createRootProvider / createProvider from @ethlete/core instead.',
176
+ },
177
+ // No route guards — handle access control inside the component itself
178
+ {
179
+ selector:
180
+ 'TSClassImplements > Identifier.expression[name=/^CanActivate$|^CanDeactivate$|^CanActivateChild$|^CanMatch$|^CanLoad$/]',
181
+ message: 'No route guards. Handle access control inside the component itself.',
182
+ },
183
+ // No route resolvers — use the query library to fetch data
184
+ {
185
+ selector: "TSClassImplements > Identifier.expression[name='Resolve']",
186
+ message: 'No route resolvers. Use the query library to fetch data instead.',
187
+ },
188
+ // No barrel imports — import directly from the source file
189
+ {
190
+ selector: 'ImportDeclaration[source.value=/(^|[/])index$/]',
191
+ message:
192
+ 'No barrel imports (index files). Import directly from the source file to avoid breaking lazy loading.',
193
+ },
194
+ ],
195
+
196
+ // ── Native DOM globals ──────────────────────────────────────────────────
197
+
198
+ // No direct document/window access — use inject(DOCUMENT) / inject(WINDOW) instead
199
+ 'no-restricted-globals': [
200
+ 'error',
201
+ {
202
+ name: 'document',
203
+ message: "Use inject(DOCUMENT) from '@angular/common' instead of the global document.",
204
+ },
205
+ {
206
+ name: 'window',
207
+ message: 'Avoid accessing the global window directly. Use platform detection or a dedicated injection token.',
208
+ },
209
+ ],
210
+
211
+ // ── Custom plugin rules ─────────────────────────────────────────────────
212
+
213
+ // No inject(X).member chaining — assign to a const first
214
+ 'ethlete/no-inject-chain': 'error',
215
+
216
+ // inject(ElementRef) must use inject<ElementRef<HTMLElement>>(ElementRef) — not inject(ElementRef) or inject(ElementRef<...>)
217
+ 'ethlete/no-typed-injected-element-ref': 'error',
218
+
219
+ // No @internal on members already hidden by private/protected
220
+ 'ethlete/no-redundant-internal': 'error',
221
+
222
+ // Non-injected Angular members should use explicit accessibility when they
223
+ // participate in the reachable surface. Template/host members must be
224
+ // explicit, other implicit-public members must be public. @internal does
225
+ // not replace explicit public; protected remains valid for template-only
226
+ // members.
227
+ 'ethlete/template-member-accessibility': 'error',
228
+
229
+ // No explicit trivial return types TypeScript can infer
230
+ 'ethlete/no-trivial-return-type': 'error',
231
+
232
+ // Empty line required before return in multi-statement if-blocks (guard clauses)
233
+ 'ethlete/guard-return-newline': 'error',
234
+
235
+ // No code in subscribe() callbacks — use tap() instead
236
+ 'ethlete/no-subscribe-with-body': 'error',
237
+
238
+ // Observable variables/properties must end with $
239
+ 'ethlete/require-dollar-suffix': 'error',
240
+
241
+ // No .subscribe() inside a .pipe() callback
242
+ 'ethlete/no-subscribe-in-pipe': 'error',
243
+
244
+ // No .subscribe() inside effect() or computed()
245
+ 'ethlete/no-rxjs-in-effect': 'error',
246
+
247
+ // No readonly on reactive class properties (signals, inputs, computed, inject, etc.)
248
+ 'ethlete/no-readonly-signal': 'error',
249
+
250
+ // No SCREAMING_CASE variable names inside function bodies
251
+ 'ethlete/no-screaming-case-local': 'error',
252
+
253
+ // Prefer RxJS timer/interval/fromEvent over setTimeout/setInterval/addEventListener
254
+ 'ethlete/prefer-rxjs-timer': 'error',
255
+
256
+ // Prefer linkedSignal over signal + .set() inside effect()
257
+ 'ethlete/prefer-linked-signal': 'warn',
258
+
259
+ // No trivial wrapper methods that only forward all args to another call
260
+ 'ethlete/no-trivial-wrapper-method': 'error',
261
+
262
+ // No direct DOM manipulation — use injectRenderer() from @ethlete/core instead
263
+ 'ethlete/no-direct-dom-manipulation': 'error',
264
+
265
+ // No raw IntersectionObserver / MutationObserver / ResizeObserver — use @ethlete/core signal utils
266
+ 'ethlete/no-native-observers': 'error',
267
+
268
+ // Prefer injectViewportSize() over window.innerWidth / innerHeight / outerWidth / outerHeight
269
+ 'ethlete/prefer-viewport-size': 'warn',
270
+
271
+ // Prefer injectMediaQueryIsMatched() / injectBreakpointIsMatched() etc. over .matchMedia()
272
+ 'ethlete/prefer-match-media': 'error',
273
+
274
+ // Prefer signalElementDimensions() / signalHostElementDimensions() in reactive contexts
275
+ 'ethlete/prefer-element-dimensions': 'warn',
276
+
277
+ // Prefer signalElementScrollState() / signalHostElementScrollState() in reactive contexts
278
+ 'ethlete/prefer-scroll-state': 'warn',
279
+
280
+ // No `import type { Foo }` or `import { type Foo }` — use regular value imports
281
+ 'ethlete/no-type-only-import': 'error',
282
+
283
+ // No empty blank lines between import declarations
284
+ 'ethlete/no-empty-newlines-between-imports': 'error',
285
+
286
+ // No legacy Angular decorators — use signal-based APIs (input, output, viewChild, etc.)
287
+ // and host: {} bindings (instead of @HostBinding / @HostListener)
288
+ 'ethlete/no-legacy-angular-decorators': 'error',
289
+
290
+ // No Angular Title/Meta services — use @ethlete/core SEO utilities instead
291
+ // (injectTitleBinding, injectMetaBinding, injectLinkBinding, etc.)
292
+ 'ethlete/no-angular-seo-services': 'error',
293
+
294
+ // No JSON.parse(JSON.stringify()), structuredClone, or lodash cloneDeep / isEqual
295
+ // — use clone() / equal() from '@ethlete/core' instead
296
+ 'ethlete/prefer-clone-equal': 'error',
297
+
298
+ // No direct document.cookie access
299
+ // — use getCookie / setCookie / hasCookie / deleteCookie from '@ethlete/core' instead
300
+ 'ethlete/no-document-cookie': 'error',
301
+
302
+ // Injected providers must be private by default, and protected only when
303
+ // referenced from an Angular template or host binding.
304
+ 'ethlete/inject-member-accessibility': 'error',
305
+
306
+ // No ActivatedRoute (fully replaced by inject* router utilities from @ethlete/core)
307
+ // No inject(Router) for state reading — use injectUrl/injectRoute/injectQueryParam etc.
308
+ 'ethlete/no-angular-router-api': 'warn',
309
+
310
+ // No window.location.* reads for URL state — use injectUrl/injectRoute/injectQueryParam etc.
311
+ // No new URLSearchParams(window.location.search) — use injectQueryParams()
312
+ 'ethlete/no-window-location': 'warn',
313
+
314
+ // No inject(LOCALE_ID) — use injectLocale() from '@ethlete/core'
315
+ 'ethlete/no-locale-id': 'error',
316
+
317
+ // No class members that are pure aliases for a nested property of another member
318
+ // — widen the accessibility of the source member instead
319
+ 'ethlete/no-member-alias': 'error',
320
+
321
+ // No leading underscores on class members; auto-fix private members when safe
322
+ 'ethlete/no-leading-underscore-class-member': 'error',
323
+
324
+ // No declared-but-never-read private/protected class members
325
+ 'ethlete/no-unused-class-member': 'error',
326
+
327
+ // No direct DOM query methods (querySelector, getElementById, etc.)
328
+ // — use viewChild() / viewChildren() or contentChild() / contentChildren() instead
329
+ 'ethlete/no-dom-query': 'error',
330
+
331
+ // Remove empty imports: [] and hostDirectives: [] metadata noise
332
+ 'ethlete/no-empty-angular-metadata-arrays': 'error',
333
+
334
+ // Remove standalone metadata — standalone is implicit and should not be declared
335
+ 'ethlete/no-standalone-flag': 'error',
336
+
337
+ // ── Angular outputs ─────────────────────────────────────────────────────
338
+
339
+ // No on-prefixed outputs (onSelectDate → selectDate)
340
+ '@angular-eslint/no-output-on-prefix': 'error',
341
+
342
+ // No outputs named after native DOM events (change, click, etc.)
343
+ '@angular-eslint/no-output-native': 'error',
344
+
345
+ // ── Angular components ──────────────────────────────────────────────────
346
+
347
+ // Always use ChangeDetectionStrategy.OnPush
348
+ 'ethlete/require-on-push-change-detection': 'error',
349
+
350
+ // Always use ViewEncapsulation.None (angular-eslint's use-component-view-encapsulation does the
351
+ // opposite — it disallows None, so we use our own custom rule instead)
352
+ 'ethlete/require-view-encapsulation-none': 'error',
353
+
354
+ // Keep Angular @Component / @Directive metadata in a consistent order
355
+ 'ethlete/angular-decorator-property-order': 'error',
356
+
357
+ // Keep class members in the documented styleguide order
358
+ 'ethlete/class-member-order': 'error',
359
+
360
+ // True class constants should be readonly and use SCREAMING_CASE
361
+ 'ethlete/class-constant-property': 'error',
362
+
363
+ // Prefer shorthand hostDirectives entries and keep extended host directive configs ordered
364
+ 'ethlete/prefer-concise-angular-host-directives': 'error',
365
+
366
+ // Prefer concise Angular style metadata when there is only one entry
367
+ 'ethlete/prefer-concise-angular-style-metadata': 'error',
368
+
369
+ // No logic in pipe transform — assign to an external utility function instead
370
+ 'ethlete/no-pipe-logic': 'error',
371
+
372
+ // Routing components must use paths containing "-view" and class names ending in "ViewComponent"
373
+ 'ethlete/enforce-routing-view-naming': 'error',
374
+ },
375
+ };
376
+
377
+ /**
378
+ * Flat config entries for Angular HTML template files.
379
+ * @type {import('eslint').Linter.Config}
380
+ */
381
+ const recommendedTemplate = {
382
+ files: ['**/*.html'],
383
+ rules: {
384
+ // No $any() in templates
385
+ '@angular-eslint/template/no-any': 'error',
386
+ // Prefer plain attribute over unnecessary property binding for static strings: etIcon="foo" not [etIcon]="'foo'"
387
+ '@angular-eslint/template/prefer-static-string-properties': 'error',
388
+ },
389
+ };
390
+
391
+ /**
392
+ * Relaxed rules for test/spec files.
393
+ * Non-null assertions are common and intentional in tests.
394
+ * @type {import('eslint').Linter.Config}
395
+ */
396
+ const recommendedSpec = {
397
+ files: ['**/*.spec.ts'],
398
+ rules: {
399
+ '@typescript-eslint/no-non-null-assertion': 'off',
400
+ },
401
+ };
402
+
403
+ module.exports = { recommendedTs, recommendedTemplate, recommendedSpec };
package/src/index.js ADDED
@@ -0,0 +1,136 @@
1
+ // @ts-check
2
+ 'use strict';
3
+
4
+ const noInjectChain = require('./rules/no-inject-chain');
5
+ const noTrivialReturnType = require('./rules/no-trivial-return-type');
6
+ const noSubscribeInPipe = require('./rules/no-subscribe-in-pipe');
7
+ const noRxjsInEffect = require('./rules/no-rxjs-in-effect');
8
+ const noReadonlySignal = require('./rules/no-readonly-signal');
9
+ const noScreamingCaseLocal = require('./rules/no-screaming-case-local');
10
+ const preferRxjsTimer = require('./rules/prefer-rxjs-timer');
11
+ const preferLinkedSignal = require('./rules/prefer-linked-signal');
12
+ const noTrivialWrapperMethod = require('./rules/no-trivial-wrapper-method');
13
+ const noDirectDomManipulation = require('./rules/no-direct-dom-manipulation');
14
+ const noNativeObservers = require('./rules/no-native-observers');
15
+ const preferViewportSize = require('./rules/prefer-viewport-size');
16
+ const preferMatchMedia = require('./rules/prefer-match-media');
17
+ const preferElementDimensions = require('./rules/prefer-element-dimensions');
18
+ const preferScrollState = require('./rules/prefer-scroll-state');
19
+ const noTypeOnlyImport = require('./rules/no-type-only-import');
20
+ const guardReturnNewline = require('./rules/guard-return-newline');
21
+ const noSubscribeWithBody = require('./rules/no-subscribe-with-body');
22
+ const requireDollarSuffix = require('./rules/require-dollar-suffix');
23
+ const noPipeLogic = require('./rules/no-pipe-logic');
24
+ const enforceRoutingViewNaming = require('./rules/enforce-routing-view-naming');
25
+ const requireOnPushChangeDetection = require('./rules/require-on-push-change-detection');
26
+ const requireViewEncapsulationNone = require('./rules/require-view-encapsulation-none');
27
+ const noLegacyAngularDecorators = require('./rules/no-legacy-angular-decorators');
28
+ const noAngularSeoServices = require('./rules/no-angular-seo-services');
29
+ const preferCloneEqual = require('./rules/prefer-clone-equal');
30
+ const noDocumentCookie = require('./rules/no-document-cookie');
31
+ const injectMemberAccessibility = require('./rules/inject-member-accessibility');
32
+ const noPublicProperty = require('./rules/no-public-property');
33
+ const noRedundantInternal = require('./rules/no-redundant-internal');
34
+ const templateMemberAccessibility = require('./rules/template-member-accessibility');
35
+ const noAngularRouterApi = require('./rules/no-angular-router-api');
36
+ const noWindowLocation = require('./rules/no-window-location');
37
+ const noLocaleId = require('./rules/no-locale-id');
38
+ const noMemberAlias = require('./rules/no-member-alias');
39
+ const noUnusedClassMember = require('./rules/no-unused-class-member');
40
+ const noDomQuery = require('./rules/no-dom-query');
41
+ const noLeadingUnderscoreClassMember = require('./rules/no-leading-underscore-class-member');
42
+ const noEmptyAngularMetadataArrays = require('./rules/no-empty-angular-metadata-arrays');
43
+ const noStandaloneFlag = require('./rules/no-standalone-flag');
44
+ const noEmptyNewlinesBetweenImports = require('./rules/no-empty-newlines-between-imports');
45
+ const angularDecoratorPropertyOrder = require('./rules/angular-decorator-property-order');
46
+ const classMemberOrder = require('./rules/class-member-order');
47
+ const classConstantProperty = require('./rules/class-constant-property');
48
+ const preferConciseAngularHostDirectives = require('./rules/prefer-concise-angular-host-directives');
49
+ const preferConciseAngularStyleMetadata = require('./rules/prefer-concise-angular-style-metadata');
50
+ const noTypedInjectedElementRef = require('./rules/no-typed-injected-element-ref');
51
+ const { recommendedTs, recommendedTemplate, recommendedSpec } = require('./configs/recommended');
52
+
53
+ /** @type {import('eslint').ESLint.Plugin} */
54
+ const plugin = {
55
+ meta: {
56
+ name: '@ethlete/eslint-plugin',
57
+ version: '0.0.1',
58
+ },
59
+ rules: {
60
+ 'no-inject-chain': noInjectChain,
61
+ 'no-trivial-return-type': noTrivialReturnType,
62
+ 'no-subscribe-in-pipe': noSubscribeInPipe,
63
+ 'no-rxjs-in-effect': noRxjsInEffect,
64
+ 'no-readonly-signal': noReadonlySignal,
65
+ 'no-screaming-case-local': noScreamingCaseLocal,
66
+ 'prefer-rxjs-timer': preferRxjsTimer,
67
+ 'prefer-linked-signal': preferLinkedSignal,
68
+ 'no-trivial-wrapper-method': noTrivialWrapperMethod,
69
+ 'no-direct-dom-manipulation': noDirectDomManipulation,
70
+ 'no-native-observers': noNativeObservers,
71
+ 'prefer-viewport-size': preferViewportSize,
72
+ 'prefer-match-media': preferMatchMedia,
73
+ 'prefer-element-dimensions': preferElementDimensions,
74
+ 'prefer-scroll-state': preferScrollState,
75
+ 'no-type-only-import': noTypeOnlyImport,
76
+ 'guard-return-newline': guardReturnNewline,
77
+ 'no-subscribe-with-body': noSubscribeWithBody,
78
+ 'require-dollar-suffix': requireDollarSuffix,
79
+ 'no-pipe-logic': noPipeLogic,
80
+ 'enforce-routing-view-naming': enforceRoutingViewNaming,
81
+ 'require-on-push-change-detection': requireOnPushChangeDetection,
82
+ 'require-view-encapsulation-none': requireViewEncapsulationNone,
83
+ 'no-legacy-angular-decorators': noLegacyAngularDecorators,
84
+ 'no-angular-seo-services': noAngularSeoServices,
85
+ 'prefer-clone-equal': preferCloneEqual,
86
+ 'no-document-cookie': noDocumentCookie,
87
+ 'inject-member-accessibility': injectMemberAccessibility,
88
+ 'no-public-property': noPublicProperty,
89
+ 'no-redundant-internal': noRedundantInternal,
90
+ 'template-member-accessibility': templateMemberAccessibility,
91
+ 'no-angular-router-api': noAngularRouterApi,
92
+ 'no-window-location': noWindowLocation,
93
+ 'no-locale-id': noLocaleId,
94
+ 'no-member-alias': noMemberAlias,
95
+ 'no-unused-class-member': noUnusedClassMember,
96
+ 'no-dom-query': noDomQuery,
97
+ 'no-leading-underscore-class-member': noLeadingUnderscoreClassMember,
98
+ 'no-empty-angular-metadata-arrays': noEmptyAngularMetadataArrays,
99
+ 'no-standalone-flag': noStandaloneFlag,
100
+ 'no-empty-newlines-between-imports': noEmptyNewlinesBetweenImports,
101
+ 'angular-decorator-property-order': angularDecoratorPropertyOrder,
102
+ 'class-member-order': classMemberOrder,
103
+ 'class-constant-property': classConstantProperty,
104
+ 'prefer-concise-angular-host-directives': preferConciseAngularHostDirectives,
105
+ 'prefer-concise-angular-style-metadata': preferConciseAngularStyleMetadata,
106
+ 'no-typed-injected-element-ref': noTypedInjectedElementRef,
107
+ },
108
+ };
109
+
110
+ // Inject the plugin reference into the configs so consumers don't have to
111
+ /** @type {import('eslint').Linter.Config} */
112
+ const recommendedTsWithPlugin = {
113
+ ...recommendedTs,
114
+ plugins: { ethlete: plugin },
115
+ };
116
+
117
+ /** @type {import('eslint').Linter.Config} */
118
+ const recommendedTemplateWithPlugin = {
119
+ ...recommendedTemplate,
120
+ };
121
+
122
+ const configs = {
123
+ /** TypeScript rules (merged into a files: ['**\/*.ts'] block) */
124
+ recommendedTs: recommendedTsWithPlugin,
125
+ /** Angular template rules (merged into a files: ['**\/*.html'] block) */
126
+ recommendedTemplate: recommendedTemplateWithPlugin,
127
+ /**
128
+ * Both together as a flat array — the most common usage:
129
+ * export default [...baseConfig, ...ethlete.configs.recommended]
130
+ */
131
+ recommended: [recommendedTsWithPlugin, recommendedTemplateWithPlugin, recommendedSpec],
132
+ };
133
+
134
+ const ethletePlugin = { ...plugin, configs };
135
+
136
+ module.exports = ethletePlugin;