@tianjos/eslint-plugin-elegant 0.7.0 → 0.7.1

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
@@ -189,12 +189,36 @@ Public state should be `readonly` so callers cannot break an aggregate's
189
189
  invariants. A declared `private`/`protected` field is allowed, and so is any
190
190
  `readonly` member.
191
191
 
192
+ A **decorated** property is allowed by default. `@Column`, `@IsString` and
193
+ friends assign the field from outside the class, so `readonly` would be a lie
194
+ and the property is framework shape rather than state the class chose to carry
195
+ — a NestJS DTO or a TypeORM entity is a wall of them. Pass
196
+ `{ ignoreDecorated: false }` to hold them to the same standard.
197
+
198
+ ```ts
199
+ // passes — the framework populates these
200
+ class BankDto {
201
+ @IsString() code: string;
202
+ @IsOptional() ispb_code?: string;
203
+ }
204
+
205
+ // reported — state the class chose to expose
206
+ class Money {
207
+ amount = 0;
208
+ }
209
+ ```
210
+
192
211
  Constructor parameter properties are covered at **every** visibility by
193
212
  default, which declared fields are not: a `private repo: Repository<Proposal>`
194
213
  that nobody marked `readonly` can still be swapped from inside, and unlike a
195
214
  declared field it is a collaborator the container handed you. Set
196
215
  `{ parameterProperties: 'public' }` to keep the rule to what its name says.
197
216
 
217
+ `ignoreDecorated` deliberately does not reach parameter properties either. A
218
+ decorator on a parameter is injection (`@Inject(TOKEN)`), which supplies a
219
+ collaborator rather than populating a field, so it still has no business being
220
+ reassignable. `max-class-fields` draws the same line for the same reason.
221
+
198
222
  This rule asks whether a field is *declared* changeable. Its behavioural
199
223
  counterpart is `no-self-mutation`, which asks whether anything actually
200
224
  changes it.
package/dist/index.d.ts CHANGED
@@ -28,6 +28,7 @@ declare const rules: {
28
28
  };
29
29
  'no-public-mutable-props': TSESLint.RuleModule<"mutableProp", [{
30
30
  parameterProperties: "public" | "all";
31
+ ignoreDecorated: boolean;
31
32
  }], unknown, TSESLint.RuleListener> & {
32
33
  name: string;
33
34
  };
@@ -1,6 +1,9 @@
1
- type Options = [{
2
- parameterProperties: 'public' | 'all';
3
- }];
1
+ type Options = [
2
+ {
3
+ parameterProperties: 'public' | 'all';
4
+ ignoreDecorated: boolean;
5
+ }
6
+ ];
4
7
  declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"mutableProp", Options, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
5
8
  name: string;
6
9
  };
@@ -21,6 +21,17 @@ const keyName = (key) => {
21
21
  const isMutable = (node, scope) => !node.readonly &&
22
22
  node.accessibility !== undefined &&
23
23
  (scope === 'all' || node.accessibility === 'public');
24
+ /**
25
+ * A property a decorator maps to a table or a payload. `@Column`, `@IsString`
26
+ * and friends assign it from outside the class, so `readonly` would be a lie
27
+ * and the field is framework shape rather than state the class chose to carry.
28
+ *
29
+ * Deliberately does not reach parameter properties: a decorator on a parameter
30
+ * is injection (`@Inject(TOKEN)`), so the field is a genuine collaborator and
31
+ * still has no business being reassignable. `max-class-fields` draws the same
32
+ * line for the same reason.
33
+ */
34
+ const isMapped = (node) => node.decorators.length > 0;
24
35
  exports.default = (0, createRule_1.createRule)({
25
36
  name: 'no-public-mutable-props',
26
37
  meta: {
@@ -36,18 +47,22 @@ exports.default = (0, createRule_1.createRule)({
36
47
  type: 'object',
37
48
  properties: {
38
49
  parameterProperties: { type: 'string', enum: ['public', 'all'] },
50
+ ignoreDecorated: { type: 'boolean' },
39
51
  },
40
52
  additionalProperties: false,
41
53
  },
42
54
  ],
43
55
  },
44
- defaultOptions: [{ parameterProperties: 'all' }],
45
- create(context, [{ parameterProperties }]) {
56
+ defaultOptions: [{ parameterProperties: 'all', ignoreDecorated: true }],
57
+ create(context, [{ parameterProperties, ignoreDecorated }]) {
46
58
  return {
47
59
  PropertyDefinition(node) {
48
60
  if (node.readonly || isHidden(node.accessibility)) {
49
61
  return;
50
62
  }
63
+ if (ignoreDecorated && isMapped(node)) {
64
+ return;
65
+ }
51
66
  context.report({
52
67
  node: node.key,
53
68
  messageId: 'mutableProp',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tianjos/eslint-plugin-elegant",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Opinionated ESLint rules for elegant, behavior-rich TypeScript: honest types, encapsulated state, small uncoupled classes, guard-clause flow, and structured logging. Built for NestJS and DDD codebases.",
5
5
  "keywords": [
6
6
  "eslint",