@ethlete/eslint-plugin 0.1.0-next.1 → 0.1.0-next.10

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/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0-next.10
4
+
5
+ ### Minor Changes
6
+
7
+ - [`4e9f2b4`](https://github.com/ethlete-io/ethdk/commit/4e9f2b4d12335fafef192350aef8ffc584996a91) Thanks [@TomTomB](https://github.com/TomTomB)! - Make the OnPush change detection rules Angular-version aware. Since OnPush is the default from Angular 22, declaring it is now redundant:
8
+ - Add `no-redundant-on-push-change-detection`, which flags and auto-fixes explicit `changeDetection: ChangeDetectionStrategy.OnPush` on `@Component`, and removes the now-unused `ChangeDetectionStrategy` import when it is no longer referenced. Active on Angular >= 22.
9
+ - `require-on-push-change-detection` now only applies on Angular <= 21, where OnPush is opt-in.
10
+
11
+ Both rules resolve the workspace's Angular major version automatically (overridable via `settings.ethlete.angularMajor`) and stay inert when they don't apply, so exactly one of them is ever active for a given Angular version.
12
+
13
+ ## 0.1.0-next.9
14
+
15
+ ### Patch Changes
16
+
17
+ - [`b323ef6`](https://github.com/ethlete-io/ethdk/commit/b323ef66130d196e5c893e844d50ecfc85487373) Thanks [@TomTomB](https://github.com/TomTomB)! - `class-member-order`: recognize custom `injectXyz()` helper functions (not just the raw `inject()` call) as inject-group members, so they're required to be declared before inputs/outputs/queries/properties like other injected dependencies.
18
+
19
+ ## 0.1.0-next.8
20
+
21
+ ### Minor Changes
22
+
23
+ - [`edea44b`](https://github.com/ethlete-io/ethdk/commit/edea44bf1c494420f02b545202f4b24db9a6395c) Thanks [@TomTomB](https://github.com/TomTomB)! - Update to angular 22
24
+
25
+ ## 0.1.0-next.7
26
+
27
+ ### Patch Changes
28
+
29
+ - [`7e8ac6b`](https://github.com/ethlete-io/ethdk/commit/7e8ac6bf18f580289c7c7966c6c33a2a7f077299) Thanks [@TomTomB](https://github.com/TomTomB)! - Fix false positives in pipes
30
+
31
+ ## 0.1.0-next.6
32
+
33
+ ### Patch Changes
34
+
35
+ - [`b73a127`](https://github.com/ethlete-io/ethdk/commit/b73a127002a06e3aa0c4e7e977b1ad1f3e04e7e6) Thanks [@TomTomB](https://github.com/TomTomB)! - Bump yet again, final one for sure, pinky promise
36
+
37
+ ## 0.1.0-beta.5
38
+
39
+ ### Patch Changes
40
+
41
+ - [`ddb5d09`](https://github.com/ethlete-io/ethdk/commit/ddb5d09e4bc56e18cc8c228aa78a200441e7a766) Thanks [@TomTomB](https://github.com/TomTomB)! - Bump to beta
42
+
43
+ ## 0.1.0-next.4
44
+
45
+ ### Patch Changes
46
+
47
+ - [`a690217`](https://github.com/ethlete-io/ethdk/commit/a6902172efd9bd1956a16237e79acbfbd816d946) Thanks [@TomTomB](https://github.com/TomTomB)! - Version bump only
48
+
49
+ ## 0.1.0-next.3
50
+
51
+ ### Patch Changes
52
+
53
+ - [`5d1d3ac`](https://github.com/ethlete-io/ethdk/commit/5d1d3accbd4a7657bc50cdc0653a7ca24fe761e2) Thanks [@TomTomB](https://github.com/TomTomB)! - Fix minor lint edge cases
54
+
55
+ ## 0.1.0-next.2
56
+
57
+ ### Patch Changes
58
+
59
+ - [#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
60
+
3
61
  ## 0.1.0-next.1
4
62
 
5
63
  ### Patch Changes
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@ethlete/eslint-plugin",
3
- "version": "0.1.0-next.1",
3
+ "version": "0.1.0-next.10",
4
4
  "license": "MIT",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
7
7
  "peerDependencies": {
8
8
  "@angular-eslint/eslint-plugin-template": ">=21.0.0",
9
+ "@angular/core": "22.0.5",
9
10
  "@typescript-eslint/eslint-plugin": ">=8.0.0",
10
11
  "eslint": ">=9.0.0",
11
- "tslib": "2.8.1"
12
+ "tslib": "2.8.1",
13
+ "typescript": "6.0.3"
12
14
  }
13
15
  }
@@ -71,10 +71,16 @@ const recommendedTs = {
71
71
  },
72
72
  {
73
73
  selector: ['variable', 'parameter', 'property', 'parameterProperty', 'accessor'],
74
- leadingUnderscore: 'forbid',
74
+ leadingUnderscore: 'allow',
75
75
  trailingUnderscore: 'forbid',
76
76
  format: ['camelCase', 'UPPER_CASE', 'PascalCase'],
77
77
  },
78
+ {
79
+ selector: 'method',
80
+ leadingUnderscore: 'allow',
81
+ trailingUnderscore: 'forbid',
82
+ format: null,
83
+ },
78
84
  {
79
85
  selector: 'typeLike',
80
86
  leadingUnderscore: 'forbid',
@@ -133,11 +139,6 @@ const recommendedTs = {
133
139
  selector: 'FunctionExpression[async=true]',
134
140
  message: 'No async/await. Use RxJS for all async operations.',
135
141
  },
136
- // No public keyword on class members
137
- {
138
- selector: "MethodDefinition[accessibility='public']",
139
- message: 'No public keyword on class members.',
140
- },
141
142
  // No static class members
142
143
  {
143
144
  selector: 'MethodDefinition[static=true]',
@@ -190,6 +191,12 @@ const recommendedTs = {
190
191
  message:
191
192
  'No barrel imports (index files). Import directly from the source file to avoid breaking lazy loading.',
192
193
  },
194
+ // No on-prefixed method names — name the method after what it does, not that it handles an event
195
+ {
196
+ selector: 'MethodDefinition[key.name=/^on[A-Z]/]',
197
+ message:
198
+ 'No on-prefixed method names. Name the method after what it does (e.g. onLayoutChange -> syncGridItemsWithLayout).',
199
+ },
193
200
  ],
194
201
 
195
202
  // ── Native DOM globals ──────────────────────────────────────────────────
@@ -212,8 +219,18 @@ const recommendedTs = {
212
219
  // No inject(X).member chaining — assign to a const first
213
220
  'ethlete/no-inject-chain': 'error',
214
221
 
215
- // No public properties except explicit injected external APIs
216
- 'ethlete/no-public-property': 'error',
222
+ // inject(ElementRef) must use inject<ElementRef<HTMLElement>>(ElementRef) — not inject(ElementRef) or inject(ElementRef<...>)
223
+ 'ethlete/no-typed-injected-element-ref': 'error',
224
+
225
+ // No @internal on members already hidden by private/protected
226
+ 'ethlete/no-redundant-internal': 'error',
227
+
228
+ // Non-injected Angular members should use explicit accessibility when they
229
+ // participate in the reachable surface. Template/host members must be
230
+ // explicit, other implicit-public members must be public. @internal does
231
+ // not replace explicit public; protected remains valid for template-only
232
+ // members.
233
+ 'ethlete/template-member-accessibility': 'error',
217
234
 
218
235
  // No explicit trivial return types TypeScript can infer
219
236
  'ethlete/no-trivial-return-type': 'error',
@@ -269,6 +286,9 @@ const recommendedTs = {
269
286
  // No `import type { Foo }` or `import { type Foo }` — use regular value imports
270
287
  'ethlete/no-type-only-import': 'error',
271
288
 
289
+ // No empty blank lines between import declarations
290
+ 'ethlete/no-empty-newlines-between-imports': 'error',
291
+
272
292
  // No legacy Angular decorators — use signal-based APIs (input, output, viewChild, etc.)
273
293
  // and host: {} bindings (instead of @HostBinding / @HostListener)
274
294
  'ethlete/no-legacy-angular-decorators': 'error',
@@ -304,6 +324,9 @@ const recommendedTs = {
304
324
  // — widen the accessibility of the source member instead
305
325
  'ethlete/no-member-alias': 'error',
306
326
 
327
+ // No leading underscores on class members; auto-fix private members when safe
328
+ 'ethlete/no-leading-underscore-class-member': 'error',
329
+
307
330
  // No declared-but-never-read private/protected class members
308
331
  'ethlete/no-unused-class-member': 'error',
309
332
 
@@ -311,6 +334,12 @@ const recommendedTs = {
311
334
  // — use viewChild() / viewChildren() or contentChild() / contentChildren() instead
312
335
  'ethlete/no-dom-query': 'error',
313
336
 
337
+ // Remove empty imports: [] and hostDirectives: [] metadata noise
338
+ 'ethlete/no-empty-angular-metadata-arrays': 'error',
339
+
340
+ // Remove standalone metadata — standalone is implicit and should not be declared
341
+ 'ethlete/no-standalone-flag': 'error',
342
+
314
343
  // ── Angular outputs ─────────────────────────────────────────────────────
315
344
 
316
345
  // No on-prefixed outputs (onSelectDate → selectDate)
@@ -321,8 +350,13 @@ const recommendedTs = {
321
350
 
322
351
  // ── Angular components ──────────────────────────────────────────────────
323
352
 
324
- // Always use ChangeDetectionStrategy.OnPush
325
- '@angular-eslint/prefer-on-push-component-change-detection': 'error',
353
+ // Require ChangeDetectionStrategy.OnPush on Angular <= 21, where it is opt-in
354
+ // (version-aware: inert on Angular 22+, where OnPush is the default).
355
+ 'ethlete/require-on-push-change-detection': 'error',
356
+
357
+ // No redundant ChangeDetectionStrategy.OnPush — it is the default since Angular 22
358
+ // (version-aware: inert on older Angular). Also removes the now-unused import.
359
+ 'ethlete/no-redundant-on-push-change-detection': 'error',
326
360
 
327
361
  // Always use ViewEncapsulation.None (angular-eslint's use-component-view-encapsulation does the
328
362
  // opposite — it disallows None, so we use our own custom rule instead)
@@ -331,6 +365,12 @@ const recommendedTs = {
331
365
  // Keep Angular @Component / @Directive metadata in a consistent order
332
366
  'ethlete/angular-decorator-property-order': 'error',
333
367
 
368
+ // Keep class members in the documented styleguide order
369
+ 'ethlete/class-member-order': 'error',
370
+
371
+ // True class constants should be readonly and use SCREAMING_CASE
372
+ 'ethlete/class-constant-property': 'error',
373
+
334
374
  // Prefer shorthand hostDirectives entries and keep extended host directive configs ordered
335
375
  'ethlete/prefer-concise-angular-host-directives': 'error',
336
376
 
@@ -359,4 +399,16 @@ const recommendedTemplate = {
359
399
  },
360
400
  };
361
401
 
362
- module.exports = { recommendedTs, recommendedTemplate };
402
+ /**
403
+ * Relaxed rules for test/spec files.
404
+ * Non-null assertions are common and intentional in tests.
405
+ * @type {import('eslint').Linter.Config}
406
+ */
407
+ const recommendedSpec = {
408
+ files: ['**/*.spec.ts'],
409
+ rules: {
410
+ '@typescript-eslint/no-non-null-assertion': 'off',
411
+ },
412
+ };
413
+
414
+ module.exports = { recommendedTs, recommendedTemplate, recommendedSpec };
package/src/index.js CHANGED
@@ -22,6 +22,8 @@ const noSubscribeWithBody = require('./rules/no-subscribe-with-body');
22
22
  const requireDollarSuffix = require('./rules/require-dollar-suffix');
23
23
  const noPipeLogic = require('./rules/no-pipe-logic');
24
24
  const enforceRoutingViewNaming = require('./rules/enforce-routing-view-naming');
25
+ const noRedundantOnPushChangeDetection = require('./rules/no-redundant-on-push-change-detection');
26
+ const requireOnPushChangeDetection = require('./rules/require-on-push-change-detection');
25
27
  const requireViewEncapsulationNone = require('./rules/require-view-encapsulation-none');
26
28
  const noLegacyAngularDecorators = require('./rules/no-legacy-angular-decorators');
27
29
  const noAngularSeoServices = require('./rules/no-angular-seo-services');
@@ -29,16 +31,25 @@ const preferCloneEqual = require('./rules/prefer-clone-equal');
29
31
  const noDocumentCookie = require('./rules/no-document-cookie');
30
32
  const injectMemberAccessibility = require('./rules/inject-member-accessibility');
31
33
  const noPublicProperty = require('./rules/no-public-property');
34
+ const noRedundantInternal = require('./rules/no-redundant-internal');
35
+ const templateMemberAccessibility = require('./rules/template-member-accessibility');
32
36
  const noAngularRouterApi = require('./rules/no-angular-router-api');
33
37
  const noWindowLocation = require('./rules/no-window-location');
34
38
  const noLocaleId = require('./rules/no-locale-id');
35
39
  const noMemberAlias = require('./rules/no-member-alias');
36
40
  const noUnusedClassMember = require('./rules/no-unused-class-member');
37
41
  const noDomQuery = require('./rules/no-dom-query');
42
+ const noLeadingUnderscoreClassMember = require('./rules/no-leading-underscore-class-member');
43
+ const noEmptyAngularMetadataArrays = require('./rules/no-empty-angular-metadata-arrays');
44
+ const noStandaloneFlag = require('./rules/no-standalone-flag');
45
+ const noEmptyNewlinesBetweenImports = require('./rules/no-empty-newlines-between-imports');
38
46
  const angularDecoratorPropertyOrder = require('./rules/angular-decorator-property-order');
47
+ const classMemberOrder = require('./rules/class-member-order');
48
+ const classConstantProperty = require('./rules/class-constant-property');
39
49
  const preferConciseAngularHostDirectives = require('./rules/prefer-concise-angular-host-directives');
40
50
  const preferConciseAngularStyleMetadata = require('./rules/prefer-concise-angular-style-metadata');
41
- const { recommendedTs, recommendedTemplate } = require('./configs/recommended');
51
+ const noTypedInjectedElementRef = require('./rules/no-typed-injected-element-ref');
52
+ const { recommendedTs, recommendedTemplate, recommendedSpec } = require('./configs/recommended');
42
53
 
43
54
  /** @type {import('eslint').ESLint.Plugin} */
44
55
  const plugin = {
@@ -68,6 +79,8 @@ const plugin = {
68
79
  'require-dollar-suffix': requireDollarSuffix,
69
80
  'no-pipe-logic': noPipeLogic,
70
81
  'enforce-routing-view-naming': enforceRoutingViewNaming,
82
+ 'no-redundant-on-push-change-detection': noRedundantOnPushChangeDetection,
83
+ 'require-on-push-change-detection': requireOnPushChangeDetection,
71
84
  'require-view-encapsulation-none': requireViewEncapsulationNone,
72
85
  'no-legacy-angular-decorators': noLegacyAngularDecorators,
73
86
  'no-angular-seo-services': noAngularSeoServices,
@@ -75,15 +88,24 @@ const plugin = {
75
88
  'no-document-cookie': noDocumentCookie,
76
89
  'inject-member-accessibility': injectMemberAccessibility,
77
90
  'no-public-property': noPublicProperty,
91
+ 'no-redundant-internal': noRedundantInternal,
92
+ 'template-member-accessibility': templateMemberAccessibility,
78
93
  'no-angular-router-api': noAngularRouterApi,
79
94
  'no-window-location': noWindowLocation,
80
95
  'no-locale-id': noLocaleId,
81
96
  'no-member-alias': noMemberAlias,
82
97
  'no-unused-class-member': noUnusedClassMember,
83
98
  'no-dom-query': noDomQuery,
99
+ 'no-leading-underscore-class-member': noLeadingUnderscoreClassMember,
100
+ 'no-empty-angular-metadata-arrays': noEmptyAngularMetadataArrays,
101
+ 'no-standalone-flag': noStandaloneFlag,
102
+ 'no-empty-newlines-between-imports': noEmptyNewlinesBetweenImports,
84
103
  'angular-decorator-property-order': angularDecoratorPropertyOrder,
104
+ 'class-member-order': classMemberOrder,
105
+ 'class-constant-property': classConstantProperty,
85
106
  'prefer-concise-angular-host-directives': preferConciseAngularHostDirectives,
86
107
  'prefer-concise-angular-style-metadata': preferConciseAngularStyleMetadata,
108
+ 'no-typed-injected-element-ref': noTypedInjectedElementRef,
87
109
  },
88
110
  };
89
111
 
@@ -108,7 +130,7 @@ const configs = {
108
130
  * Both together as a flat array — the most common usage:
109
131
  * export default [...baseConfig, ...ethlete.configs.recommended]
110
132
  */
111
- recommended: [recommendedTsWithPlugin, recommendedTemplateWithPlugin],
133
+ recommended: [recommendedTsWithPlugin, recommendedTemplateWithPlugin, recommendedSpec],
112
134
  };
113
135
 
114
136
  const ethletePlugin = { ...plugin, configs };
@@ -29,28 +29,16 @@ const DECORATOR_CONFIG = {
29
29
  'outputs',
30
30
  'queries',
31
31
  'preserveWhitespaces',
32
- 'standalone',
33
32
  'schemas',
34
33
  'jit',
35
34
  'exportAs',
36
35
  ],
37
36
  orderText:
38
- 'selector, template/templateUrl, styleUrl/styleUrls, encapsulation, changeDetection, imports, providers, viewProviders, animations, hostDirectives, host, styles, inputs, outputs, queries, preserveWhitespaces, standalone, schemas, jit, exportAs',
37
+ 'selector, template/templateUrl, styleUrl/styleUrls, encapsulation, changeDetection, imports, providers, viewProviders, animations, hostDirectives, host, styles, inputs, outputs, queries, preserveWhitespaces, schemas, jit, exportAs',
39
38
  },
40
39
  Directive: {
41
- order: [
42
- 'selector',
43
- 'exportAs',
44
- 'providers',
45
- 'inputs',
46
- 'outputs',
47
- 'queries',
48
- 'hostDirectives',
49
- 'host',
50
- 'standalone',
51
- 'jit',
52
- ],
53
- orderText: 'selector, exportAs, providers, inputs, outputs, queries, hostDirectives, host, standalone, jit',
40
+ order: ['selector', 'exportAs', 'providers', 'inputs', 'outputs', 'queries', 'hostDirectives', 'host', 'jit'],
41
+ orderText: 'selector, exportAs, providers, inputs, outputs, queries, hostDirectives, host, jit',
54
42
  },
55
43
  };
56
44