@ethlete/eslint-plugin 0.1.0-next.8 → 1.0.0-next.11

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,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0-next.11
4
+
5
+ ### Major Changes
6
+
7
+ - [#3016](https://github.com/ethlete-io/ethdk/pull/3016) [`396bdfb`](https://github.com/ethlete-io/ethdk/commit/396bdfb50dc51d2de0156dd7b7cc0ae3b21dfe9b) Thanks [@github-actions](https://github.com/apps/github-actions)! - Remove the `no-public-property` rule. It contradicted `template-member-accessibility`, which requires an explicit `public` modifier on surface members — the explicit-public style is the intended one. The rule was never part of the `recommended` config; if you enabled `ethlete/no-public-property` manually, drop it from your ESLint config.
8
+
9
+ ## 0.1.0-next.10
10
+
11
+ ### Minor Changes
12
+
13
+ - [`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:
14
+ - 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.
15
+ - `require-on-push-change-detection` now only applies on Angular <= 21, where OnPush is opt-in.
16
+
17
+ 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.
18
+
19
+ ## 0.1.0-next.9
20
+
21
+ ### Patch Changes
22
+
23
+ - [`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.
24
+
3
25
  ## 0.1.0-next.8
4
26
 
5
27
  ### Minor Changes
package/README.md CHANGED
@@ -5,7 +5,7 @@ ESLint plugin with custom rules and shareable flat configs for the Ethlete codin
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install --save-dev @ethlete/eslint-plugin
8
+ yarn add --dev @ethlete/eslint-plugin
9
9
  ```
10
10
 
11
11
  ## Usage
@@ -17,9 +17,10 @@ import ethlete from '@ethlete/eslint-plugin';
17
17
  export default [...ethlete.configs.recommended];
18
18
  ```
19
19
 
20
- ## Rules
20
+ ## Documentation
21
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 |
22
+ The full rule reference (all rules, grouped, with defaults and auto-fix support) and
23
+ config documentation live on the docs site:
24
+
25
+ - [Overview & usage](https://ethlete-sdk-docs.web.app/eslint/)
26
+ - [Rule reference](https://ethlete-sdk-docs.web.app/eslint/rules)
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@ethlete/eslint-plugin",
3
- "version": "0.1.0-next.8",
3
+ "version": "1.0.0-next.11",
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
12
  "tslib": "2.8.1",
@@ -191,6 +191,12 @@ const recommendedTs = {
191
191
  message:
192
192
  'No barrel imports (index files). Import directly from the source file to avoid breaking lazy loading.',
193
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
+ },
194
200
  ],
195
201
 
196
202
  // ── Native DOM globals ──────────────────────────────────────────────────
@@ -344,9 +350,14 @@ const recommendedTs = {
344
350
 
345
351
  // ── Angular components ──────────────────────────────────────────────────
346
352
 
347
- // Always use ChangeDetectionStrategy.OnPush
353
+ // Require ChangeDetectionStrategy.OnPush on Angular <= 21, where it is opt-in
354
+ // (version-aware: inert on Angular 22+, where OnPush is the default).
348
355
  'ethlete/require-on-push-change-detection': 'error',
349
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',
360
+
350
361
  // Always use ViewEncapsulation.None (angular-eslint's use-component-view-encapsulation does the
351
362
  // opposite — it disallows None, so we use our own custom rule instead)
352
363
  'ethlete/require-view-encapsulation-none': 'error',
package/src/index.js CHANGED
@@ -22,6 +22,7 @@ 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');
25
26
  const requireOnPushChangeDetection = require('./rules/require-on-push-change-detection');
26
27
  const requireViewEncapsulationNone = require('./rules/require-view-encapsulation-none');
27
28
  const noLegacyAngularDecorators = require('./rules/no-legacy-angular-decorators');
@@ -29,7 +30,6 @@ const noAngularSeoServices = require('./rules/no-angular-seo-services');
29
30
  const preferCloneEqual = require('./rules/prefer-clone-equal');
30
31
  const noDocumentCookie = require('./rules/no-document-cookie');
31
32
  const injectMemberAccessibility = require('./rules/inject-member-accessibility');
32
- const noPublicProperty = require('./rules/no-public-property');
33
33
  const noRedundantInternal = require('./rules/no-redundant-internal');
34
34
  const templateMemberAccessibility = require('./rules/template-member-accessibility');
35
35
  const noAngularRouterApi = require('./rules/no-angular-router-api');
@@ -78,6 +78,7 @@ const plugin = {
78
78
  'require-dollar-suffix': requireDollarSuffix,
79
79
  'no-pipe-logic': noPipeLogic,
80
80
  'enforce-routing-view-naming': enforceRoutingViewNaming,
81
+ 'no-redundant-on-push-change-detection': noRedundantOnPushChangeDetection,
81
82
  'require-on-push-change-detection': requireOnPushChangeDetection,
82
83
  'require-view-encapsulation-none': requireViewEncapsulationNone,
83
84
  'no-legacy-angular-decorators': noLegacyAngularDecorators,
@@ -85,7 +86,6 @@ const plugin = {
85
86
  'prefer-clone-equal': preferCloneEqual,
86
87
  'no-document-cookie': noDocumentCookie,
87
88
  'inject-member-accessibility': injectMemberAccessibility,
88
- 'no-public-property': noPublicProperty,
89
89
  'no-redundant-internal': noRedundantInternal,
90
90
  'template-member-accessibility': templateMemberAccessibility,
91
91
  'no-angular-router-api': noAngularRouterApi,
@@ -81,6 +81,18 @@ const isPropertyInitializedWith = (node, apiNames) => {
81
81
  return apiName !== null && apiNames.has(apiName);
82
82
  };
83
83
 
84
+ const INJECT_HELPER_NAME_PATTERN = /^inject[A-Z]/;
85
+
86
+ /**
87
+ * @param {any} node
88
+ */
89
+ const isPropertyInitializedWithInject = (node) => {
90
+ if (node.type !== 'PropertyDefinition') return false;
91
+
92
+ const apiName = getCallRootName(node.value);
93
+ return apiName !== null && (apiName === 'inject' || INJECT_HELPER_NAME_PATTERN.test(apiName));
94
+ };
95
+
84
96
  /**
85
97
  * @param {any} node
86
98
  * @returns {TMemberGroup | null}
@@ -89,7 +101,7 @@ const getMemberGroup = (node) => {
89
101
  if (node.static) return null;
90
102
 
91
103
  if (node.type === 'PropertyDefinition') {
92
- if (isPropertyInitializedWith(node, new Set(['inject']))) {
104
+ if (isPropertyInitializedWithInject(node)) {
93
105
  return 'inject';
94
106
  }
95
107
 
@@ -0,0 +1,237 @@
1
+ // @ts-check
2
+ 'use strict';
3
+
4
+ /**
5
+ * Disallow explicit `changeDetection: ChangeDetectionStrategy.OnPush` on
6
+ * `@Component` decorators. Since Angular 22, OnPush is the default change
7
+ * detection strategy, so declaring it is redundant noise.
8
+ *
9
+ * The rule is version-aware: it only activates when the workspace's installed
10
+ * Angular is v22 or newer (or when the version cannot be determined). On older
11
+ * Angular versions — where OnPush is NOT the default — it stays silent so that
12
+ * removing it would not change behaviour.
13
+ *
14
+ * BAD (Angular >= 22):
15
+ * @Component({ selector: 'et-x', template: '', changeDetection: ChangeDetectionStrategy.OnPush })
16
+ *
17
+ * GOOD (Angular >= 22):
18
+ * @Component({ selector: 'et-x', template: '' })
19
+ */
20
+
21
+ const REQUIRED_MAJOR = 22;
22
+
23
+ /** @type {number | null | undefined} */
24
+ let cachedAngularMajor;
25
+
26
+ /**
27
+ * Detect the workspace's installed Angular major version. Cached across files.
28
+ *
29
+ * @returns {number | null} major version, or null when it cannot be determined
30
+ */
31
+ const detectAngularMajor = () => {
32
+ if (cachedAngularMajor !== undefined) return cachedAngularMajor;
33
+
34
+ try {
35
+ const pkg = require('@angular/core/package.json');
36
+ const major = Number.parseInt(String(pkg.version).split('.')[0], 10);
37
+ cachedAngularMajor = Number.isNaN(major) ? null : major;
38
+ } catch {
39
+ cachedAngularMajor = null;
40
+ }
41
+
42
+ return cachedAngularMajor;
43
+ };
44
+
45
+ /**
46
+ * Resolve the Angular major version to gate on. An explicit
47
+ * `settings.ethlete.angularMajor` wins over auto-detection — handy for
48
+ * pinning behaviour and for tests.
49
+ *
50
+ * @param {import('eslint').Rule.RuleContext} context
51
+ * @returns {number | null}
52
+ */
53
+ const getAngularMajor = (context) => {
54
+ const settings = /** @type {any} */ (context.settings);
55
+ const override = settings?.ethlete?.angularMajor;
56
+ if (typeof override === 'number') return override;
57
+
58
+ return detectAngularMajor();
59
+ };
60
+
61
+ /**
62
+ * @param {import('estree').Property['key']} key
63
+ */
64
+ const getPropertyName = (key) => {
65
+ if (key.type === 'Identifier') return key.name;
66
+ if (key.type === 'Literal' && typeof key.value === 'string') return key.value;
67
+ return null;
68
+ };
69
+
70
+ /**
71
+ * @param {import('eslint').Rule.Node} node
72
+ */
73
+ const isComponentDecorator = (node) => {
74
+ const decorator = /** @type {any} */ (node);
75
+ if (decorator.type !== 'Decorator') return false;
76
+
77
+ const expression = decorator.expression;
78
+ if (expression.type === 'CallExpression') {
79
+ return expression.callee.type === 'Identifier' && expression.callee.name === 'Component';
80
+ }
81
+
82
+ return expression.type === 'Identifier' && expression.name === 'Component';
83
+ };
84
+
85
+ /**
86
+ * @param {any} value
87
+ */
88
+ const isOnPushValue = (value) =>
89
+ value.type === 'MemberExpression' &&
90
+ value.object.type === 'Identifier' &&
91
+ value.object.name === 'ChangeDetectionStrategy' &&
92
+ value.property.type === 'Identifier' &&
93
+ value.property.name === 'OnPush';
94
+
95
+ /**
96
+ * Range that removes a property together with the comma that joins it to its
97
+ * siblings (leading comma when it is the last property, otherwise trailing).
98
+ *
99
+ * @param {import('eslint').SourceCode} sourceCode
100
+ * @param {any} metadata
101
+ * @param {any} property
102
+ */
103
+ const getPropertyRemovalRange = (sourceCode, metadata, property) => {
104
+ const properties = metadata.properties.filter((entry) => entry.type === 'Property');
105
+ const index = properties.indexOf(property);
106
+ const openingBrace = sourceCode.getFirstToken(metadata);
107
+ const closingBrace = sourceCode.getLastToken(metadata);
108
+
109
+ if (!openingBrace || !closingBrace) return property.range;
110
+ if (properties.length === 1) return [openingBrace.range[1], closingBrace.range[0]];
111
+ if (index < properties.length - 1) return [property.range[0], properties[index + 1].range[0]];
112
+
113
+ return [properties[index - 1].range[1], property.range[1]];
114
+ };
115
+
116
+ /**
117
+ * Range that removes a single named import specifier together with its comma.
118
+ *
119
+ * @param {import('eslint').SourceCode} sourceCode
120
+ * @param {any} importNode
121
+ * @param {any} specifier
122
+ */
123
+ const getSpecifierRemovalRange = (sourceCode, importNode, specifier) => {
124
+ const named = importNode.specifiers.filter((entry) => entry.type === 'ImportSpecifier');
125
+ const index = named.indexOf(specifier);
126
+
127
+ // Sole specifier of the whole import — drop the entire declaration.
128
+ if (importNode.specifiers.length === 1) return importNode.range;
129
+ if (index < named.length - 1) return [specifier.range[0], named[index + 1].range[0]];
130
+
131
+ return [named[index - 1].range[1], specifier.range[1]];
132
+ };
133
+
134
+ /**
135
+ * True when every reference of the `ChangeDetectionStrategy` binding is a
136
+ * `changeDetection: ChangeDetectionStrategy.OnPush` property value — i.e.
137
+ * removing those properties leaves the import unused and safe to drop.
138
+ *
139
+ * @param {import('eslint').Scope.Reference} reference
140
+ */
141
+ const isOnPushChangeDetectionReference = (reference) => {
142
+ const identifier = /** @type {any} */ (reference.identifier);
143
+ const member = identifier.parent;
144
+ if (!member || member.type !== 'MemberExpression' || member.object !== identifier) return false;
145
+ if (member.property.type !== 'Identifier' || member.property.name !== 'OnPush') return false;
146
+
147
+ const property = member.parent;
148
+ if (!property || property.type !== 'Property' || property.value !== member) return false;
149
+
150
+ return getPropertyName(property.key) === 'changeDetection';
151
+ };
152
+
153
+ /** @type {import('eslint').Rule.RuleModule} */
154
+ const noRedundantOnPushChangeDetection = {
155
+ meta: {
156
+ type: 'suggestion',
157
+ docs: {
158
+ description:
159
+ 'Disallow redundant `changeDetection: ChangeDetectionStrategy.OnPush`. OnPush is the default since Angular 22.',
160
+ },
161
+ fixable: 'code',
162
+ schema: [],
163
+ messages: {
164
+ redundant:
165
+ '`changeDetection: ChangeDetectionStrategy.OnPush` is redundant. OnPush is the default change detection strategy since Angular 22.',
166
+ redundantImport:
167
+ 'Remove the now-unused `ChangeDetectionStrategy` import. OnPush is the default change detection strategy since Angular 22.',
168
+ },
169
+ },
170
+ create(context) {
171
+ // Only enforce on Angular >= 22, where OnPush is the default. Stay silent on
172
+ // older versions (removing it there would change behaviour) — but enforce
173
+ // when the version is unknown, since this workspace targets modern Angular.
174
+ const angularMajor = getAngularMajor(context);
175
+ if (angularMajor !== null && angularMajor < REQUIRED_MAJOR) return {};
176
+
177
+ const sourceCode = context.sourceCode;
178
+ /** @type {any} */
179
+ let changeDetectionSpecifier = null;
180
+
181
+ return {
182
+ Decorator(node) {
183
+ if (!isComponentDecorator(node)) return;
184
+
185
+ const expression = /** @type {any} */ (node).expression;
186
+ if (expression.type !== 'CallExpression' || expression.arguments.length === 0) return;
187
+
188
+ const metadata = expression.arguments[0];
189
+ if (!metadata || metadata.type !== 'ObjectExpression') return;
190
+
191
+ const property = metadata.properties.find(
192
+ (entry) => entry.type === 'Property' && getPropertyName(entry.key) === 'changeDetection',
193
+ );
194
+ if (!property || !isOnPushValue(property.value)) return;
195
+
196
+ context.report({
197
+ node: property,
198
+ messageId: 'redundant',
199
+ fix: (fixer) => fixer.replaceTextRange(getPropertyRemovalRange(sourceCode, metadata, property), ''),
200
+ });
201
+ },
202
+
203
+ ImportDeclaration(node) {
204
+ if (node.source.value !== '@angular/core') return;
205
+
206
+ for (const specifier of node.specifiers) {
207
+ if (
208
+ specifier.type === 'ImportSpecifier' &&
209
+ specifier.imported.type === 'Identifier' &&
210
+ specifier.imported.name === 'ChangeDetectionStrategy'
211
+ ) {
212
+ changeDetectionSpecifier = specifier;
213
+ return;
214
+ }
215
+ }
216
+ },
217
+
218
+ 'Program:exit'() {
219
+ if (!changeDetectionSpecifier) return;
220
+
221
+ const [variable] = sourceCode.getDeclaredVariables(changeDetectionSpecifier);
222
+ if (!variable || variable.references.length === 0) return;
223
+ if (!variable.references.every(isOnPushChangeDetectionReference)) return;
224
+
225
+ const importNode = changeDetectionSpecifier.parent;
226
+ context.report({
227
+ node: changeDetectionSpecifier,
228
+ messageId: 'redundantImport',
229
+ fix: (fixer) =>
230
+ fixer.replaceTextRange(getSpecifierRemovalRange(sourceCode, importNode, changeDetectionSpecifier), ''),
231
+ });
232
+ },
233
+ };
234
+ },
235
+ };
236
+
237
+ module.exports = noRedundantOnPushChangeDetection;
@@ -3,6 +3,49 @@
3
3
 
4
4
  const COMPONENT_ORDER = ['selector', 'template', 'styleUrl', 'encapsulation', 'changeDetection'];
5
5
 
6
+ // OnPush became the default change detection strategy in Angular 22, so from
7
+ // v22 onward it must NOT be declared (see no-redundant-on-push-change-detection).
8
+ // This rule therefore only applies to older Angular, where OnPush is opt-in.
9
+ const MAX_MAJOR = 21;
10
+
11
+ /** @type {number | null | undefined} */
12
+ let cachedAngularMajor;
13
+
14
+ /**
15
+ * Detect the workspace's installed Angular major version. Cached across files.
16
+ *
17
+ * @returns {number | null} major version, or null when it cannot be determined
18
+ */
19
+ const detectAngularMajor = () => {
20
+ if (cachedAngularMajor !== undefined) return cachedAngularMajor;
21
+
22
+ try {
23
+ const pkg = require('@angular/core/package.json');
24
+ const major = Number.parseInt(String(pkg.version).split('.')[0], 10);
25
+ cachedAngularMajor = Number.isNaN(major) ? null : major;
26
+ } catch {
27
+ cachedAngularMajor = null;
28
+ }
29
+
30
+ return cachedAngularMajor;
31
+ };
32
+
33
+ /**
34
+ * Resolve the Angular major version to gate on. An explicit
35
+ * `settings.ethlete.angularMajor` wins over auto-detection — handy for
36
+ * pinning behaviour and for tests.
37
+ *
38
+ * @param {import('eslint').Rule.RuleContext} context
39
+ * @returns {number | null}
40
+ */
41
+ const getAngularMajor = (context) => {
42
+ const settings = /** @type {any} */ (context.settings);
43
+ const override = settings?.ethlete?.angularMajor;
44
+ if (typeof override === 'number') return override;
45
+
46
+ return detectAngularMajor();
47
+ };
48
+
6
49
  /**
7
50
  * @param {import('estree').Property['key']} key
8
51
  */
@@ -204,6 +247,12 @@ const requireOnPushChangeDetection = {
204
247
  schema: [],
205
248
  },
206
249
  create(context) {
250
+ // Only enforce on Angular <= 21, where OnPush is opt-in. On v22+ (or when
251
+ // the version is unknown — this workspace targets modern Angular) stay
252
+ // silent so no-redundant-on-push-change-detection owns the metadata.
253
+ const angularMajor = getAngularMajor(context);
254
+ if (angularMajor === null || angularMajor > MAX_MAJOR) return {};
255
+
207
256
  const sourceCode = context.sourceCode;
208
257
 
209
258
  /**
@@ -1,85 +0,0 @@
1
- // @ts-check
2
- 'use strict';
3
-
4
- const { getMemberName, isReferencedFromTemplateOrHost } = require('./internals/angular-member-visibility');
5
-
6
- /**
7
- * @param {any} node
8
- */
9
- const isInjectCall = (node) => {
10
- if (!node || node.type !== 'CallExpression') return false;
11
- return node.callee.type === 'Identifier' && node.callee.name === 'inject';
12
- };
13
-
14
- /** @type {import('eslint').Rule.RuleModule} */
15
- const noPublicProperty = {
16
- meta: {
17
- type: 'problem',
18
- docs: {
19
- description:
20
- 'Disallow explicit public on class properties, except for injected members that intentionally expose an external API.',
21
- recommended: true,
22
- },
23
- fixable: 'code',
24
- schema: [],
25
- messages: {
26
- noPublicProperty:
27
- 'No public keyword on class properties. The only exception is an injected member that intentionally exposes an external API.',
28
- shouldBeProtected:
29
- 'Member `{{name}}` should be protected instead of public because it is referenced from a template or host binding.',
30
- },
31
- },
32
- create(context) {
33
- /**
34
- * @param {any} node
35
- */
36
- const removePublicFix = (node) => {
37
- const publicToken = context.sourceCode.getFirstToken(node, (token) => token.value === 'public');
38
- const nextToken = publicToken ? context.sourceCode.getTokenAfter(publicToken) : null;
39
- if (!publicToken || !nextToken) return null;
40
-
41
- return (fixer) => fixer.removeRange([publicToken.range[0], nextToken.range[0]]);
42
- };
43
-
44
- /**
45
- * @param {any} node
46
- */
47
- const replacePublicWithProtectedFix = (node) => {
48
- const publicToken = context.sourceCode.getFirstToken(node, (token) => token.value === 'public');
49
- if (!publicToken) return null;
50
-
51
- return (fixer) => fixer.replaceText(publicToken, 'protected');
52
- };
53
-
54
- return {
55
- PropertyDefinition(node) {
56
- if (node.accessibility !== 'public') return;
57
- if (isInjectCall(node.value)) return;
58
-
59
- const classNode = node.parent && node.parent.type === 'ClassBody' ? node.parent.parent : null;
60
- const memberName = getMemberName(node);
61
- const isTemplateVisible =
62
- classNode && memberName ? isReferencedFromTemplateOrHost(classNode, memberName, context) : false;
63
-
64
- if (isTemplateVisible) {
65
- context.report({
66
- node,
67
- messageId: 'shouldBeProtected',
68
- data: { name: memberName },
69
- fix: replacePublicWithProtectedFix(node),
70
- });
71
-
72
- return;
73
- }
74
-
75
- context.report({
76
- node,
77
- messageId: 'noPublicProperty',
78
- fix: removePublicFix(node),
79
- });
80
- },
81
- };
82
- },
83
- };
84
-
85
- module.exports = noPublicProperty;