@aneuhold/eslint-config 3.0.1 → 3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aneuhold/eslint-config",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Main ESLint Configuration for personal projects",
5
5
  "main": "./src/configs/ts-lib-config.ts",
6
6
  "packageManager": "pnpm@10.33.0",
@@ -3,13 +3,16 @@ import { type ClassFrame, type ClassNode } from './types';
3
3
 
4
4
  /**
5
5
  * Creates a fresh frame for a class, scanning its members up-front for the
6
- * private names eligible to convert and the `#names` already in use.
6
+ * private names eligible to convert and the `#names` already in use. Decorated
7
+ * members are excluded and their names blocked, since TypeScript rejects
8
+ * decorators on `#private` members (`TS1206`).
7
9
  *
8
10
  * @param classNode The class declaration or expression
9
11
  */
10
12
  export const createClassFrame = (classNode: ClassNode): ClassFrame => {
11
13
  const candidateNames = new Set<string>();
12
14
  const existingPrivateNames = new Set<string>();
15
+ const decoratedNames = new Set<string>();
13
16
 
14
17
  for (const member of classNode.body.body) {
15
18
  if (
@@ -26,10 +29,20 @@ export const createClassFrame = (classNode: ClassNode): ClassFrame => {
26
29
  member.key.type === AST_NODE_TYPES.Identifier &&
27
30
  member.accessibility === 'private'
28
31
  ) {
29
- candidateNames.add(member.key.name);
32
+ if (member.decorators.length > 0) {
33
+ decoratedNames.add(member.key.name);
34
+ } else {
35
+ candidateNames.add(member.key.name);
36
+ }
30
37
  }
31
38
  }
32
39
 
40
+ // A name shared with a decorated member (such as a get/set pair where only
41
+ // one carries the decorator) has to stay put, so the pair keeps matching.
42
+ for (const name of decoratedNames) {
43
+ candidateNames.delete(name);
44
+ }
45
+
33
46
  return {
34
47
  classNode,
35
48
  className: classNode.id?.name ?? null,
@@ -37,7 +50,7 @@ export const createClassFrame = (classNode: ClassNode): ClassFrame => {
37
50
  existingPrivateNames,
38
51
  thisRefs: new Map(),
39
52
  staticRefs: new Map(),
40
- blocked: new Set(),
53
+ blocked: decoratedNames,
41
54
  rebindDepth: 0,
42
55
  };
43
56
  };
@@ -11,6 +11,7 @@ export type ClassReports = { reports: MemberReport[]; targets: FixTarget[] };
11
11
  * Turns a fully-populated frame into the per-member reports and the set of
12
12
  * fixable conversion targets. Each name claims its references once, so a
13
13
  * get/set pair sharing a name doesn't rewrite the same reference twice.
14
+ * Decorated members are skipped entirely — neither reported nor converted.
14
15
  *
15
16
  * @param frame The class frame, after traversal has recorded its references
16
17
  */
@@ -33,6 +34,9 @@ export const collectClassReports = (frame: ClassFrame): ClassReports => {
33
34
  if (member.type === AST_NODE_TYPES.MethodDefinition && member.kind === 'constructor') {
34
35
  continue;
35
36
  }
37
+ if (member.decorators.length > 0) {
38
+ continue;
39
+ }
36
40
 
37
41
  const messageId: MessageId =
38
42
  member.type === AST_NODE_TYPES.MethodDefinition ? 'privateMethod' : 'privateField';
@@ -27,6 +27,13 @@ is a concise, readable shorthand that declares and assigns the field in one
27
27
  place, and `#private` has no equivalent. Flagging it would force the verbose
28
28
  field-plus-assignment form for no real benefit, so the rule permits it.
29
29
 
30
+ **Decorated members are allowed.** TypeScript rejects decorators on `#private`
31
+ fields and methods with `TS1206: Decorators are not valid here.`, so a decorated
32
+ member has no `#` form to convert to. Reporting one would only force an
33
+ `eslint-disable` comment for something that cannot be fixed, so the rule skips
34
+ it — no report, and neither the declaration nor its `this.name` references are
35
+ rewritten.
36
+
30
37
  ## Autofix
31
38
 
32
39
  None. Converting `private` to `#` requires renaming every reference
@@ -64,6 +71,10 @@ class Counter {
64
71
  class Service {
65
72
  // Parameter properties are allowed.
66
73
  constructor(private readonly client: Client) {}
74
+
75
+ // Decorated members are allowed.
76
+ @WebSocketServer()
77
+ private server!: SocketServer;
67
78
  }
68
79
  ```
69
80
 
@@ -27,6 +27,9 @@ ruleTester.run('no-private-modifier', noPrivateModifier, {
27
27
  // Constructor parameter properties are allowed (no `#` shorthand exists).
28
28
  { code: `class Foo { constructor(private foo: string) {} }` },
29
29
  { code: `class Foo { constructor(private readonly foo: string) {} }` },
30
+ // Decorated members are allowed (decorators are invalid on `#private`).
31
+ { code: `class Foo { @Inject() private client!: Client; }` },
32
+ { code: `class Foo { @Log() private helper(): void {} }` },
30
33
  // `public`/`protected`/no-modifier members are out of scope.
31
34
  { code: `class Foo { public count = 0; }` },
32
35
  { code: `class Foo { protected helper(): void {} }` },
@@ -95,6 +98,13 @@ ruleTester.run('no-private-modifier', noPrivateModifier, {
95
98
  errors: [{ messageId: 'privateField' }],
96
99
  output: null,
97
100
  },
101
+ // A decorated member sits out the conversion entirely, references included,
102
+ // while an undecorated sibling still converts.
103
+ {
104
+ code: `class Foo { @Inject() private client!: Client; private count = 0; run() { this.client.send(this.count); } }`,
105
+ errors: [{ messageId: 'privateField' }],
106
+ output: `class Foo { @Inject() private client!: Client; #count = 0; run() { this.client.send(this.#count); } }`,
107
+ },
98
108
  // A colliding `#count` already exists, so the rename is unsafe — report only.
99
109
  {
100
110
  code: `class Foo { #count = 1; private count = 2; }`,
@@ -10,9 +10,9 @@ import { type ClassFrame, type ClassNode } from './types';
10
10
  * syntax rather than the TypeScript `private` accessibility modifier, and
11
11
  * autofixes the conversion — declaration plus `this.x` / `ClassName.x`
12
12
  * references — whenever it can do so safely. Covers instance and static fields,
13
- * and methods (including accessors and getters/setters). Private constructors
14
- * and constructor parameter properties (`constructor(private foo)`) are
15
- * intentionally allowed, since the `#` form has no equivalent.
13
+ * and methods (including accessors and getters/setters). Private constructors,
14
+ * constructor parameter properties (`constructor(private foo)`), and decorated
15
+ * members are intentionally allowed, since the `#` form has no equivalent.
16
16
  *
17
17
  * References are gathered as ESLint makes its single pass over the file: a stack
18
18
  * tracks the class currently being traversed, and a per-class counter tracks how
@@ -31,7 +31,7 @@ export type ClassFrame = {
31
31
  thisRefs: Map<string, TSESTree.MemberExpression[]>;
32
32
  /** `ClassName.name` accesses (statics) that an autofix would rewrite. */
33
33
  staticRefs: Map<string, TSESTree.MemberExpression[]>;
34
- /** Names with a reference the fix can't safely rewrite, so won't be touched. */
34
+ /** Names the fix can't safely rewrite, so won't be touched. */
35
35
  blocked: Set<string>;
36
36
  /**
37
37
  * How many non-arrow functions are currently open inside this class without