@blumintinc/eslint-plugin-blumint 1.20.181 → 1.20.182

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/lib/index.js CHANGED
@@ -223,7 +223,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
223
223
  module.exports = {
224
224
  meta: {
225
225
  name: '@blumintinc/eslint-plugin-blumint',
226
- version: '1.20.181',
226
+ version: '1.20.182',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -48,6 +48,30 @@ function resolveMemberName(key) {
48
48
  function memberDisplayName(member) {
49
49
  return member.isEcmaPrivate ? `#${member.name}` : member.name;
50
50
  }
51
+ /**
52
+ * The member name a class field contributes, when the function under inspection is
53
+ * that field's own initializer. A field-declared helper is the same class member as
54
+ * its prototype-method spelling, so the assert- contract has to read the same name
55
+ * from both: deriving nothing here would let `assertFoo() {}` be rewritten to
56
+ * `assertFoo = () => {}` and escape the check without changing anything the check
57
+ * judges. The `value` identity test keeps a function nested somewhere inside a
58
+ * field's initializer — a callback, a wrapped factory — from borrowing the field's
59
+ * name.
60
+ *
61
+ * A computed key evaluates an expression at class-definition time instead of
62
+ * spelling a member name (`[assertKey] = ...` names a variable, not the member), so
63
+ * it contributes no name to a naming convention. A `declare` field states a type
64
+ * with no implementation, so it has no control flow that could throw.
65
+ */
66
+ function propertyDefinitionMemberName(parent, node) {
67
+ if (parent.type !== utils_1.AST_NODE_TYPES.PropertyDefinition) {
68
+ return null;
69
+ }
70
+ if (parent.value !== node || parent.computed || parent.declare) {
71
+ return null;
72
+ }
73
+ return resolveMemberName(parent.key);
74
+ }
51
75
  /**
52
76
  * Whether a member expression's property names an assert helper. The `#` sigil is a
53
77
  * privacy marker rather than part of the identifier the naming convention governs,
@@ -607,6 +631,9 @@ exports.enforceAssertThrows = (0, createRule_1.createRule)({
607
631
  parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
608
632
  member = { name: parent.id.name, isEcmaPrivate: false };
609
633
  }
634
+ else if (parent) {
635
+ member = propertyDefinitionMemberName(parent, node);
636
+ }
610
637
  }
611
638
  // The bare name drives the assert- convention; the report quotes the name as
612
639
  // written so a `#assertFoo` finding is not read as its public namesake.
@@ -1505,6 +1505,29 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
1505
1505
  });
1506
1506
  }
1507
1507
  }
1508
+ /**
1509
+ * Whether a class field's value is a function whose DECLARED return type is
1510
+ * `boolean`.
1511
+ *
1512
+ * Booleanness is read from the return annotation alone, which is exactly
1513
+ * what the method arm requires. Routing the value through
1514
+ * `returnsBooleanValue` instead would additionally accept an un-annotated
1515
+ * arrow whose expression body merely looks boolean (`valid = () => x > 0`),
1516
+ * whose method counterpart (`valid() { return x > 0; }`) stays silent — so
1517
+ * the two spellings would disagree in the opposite direction. A type
1518
+ * predicate (`(v): v is Foo => …`) is excluded by the same keying, matching
1519
+ * the method arm's explicit predicate carve-out.
1520
+ */
1521
+ function declaresBooleanReturningFunction(node) {
1522
+ const value = node.value;
1523
+ if (value?.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
1524
+ value?.type !== utils_1.AST_NODE_TYPES.FunctionExpression) {
1525
+ return false;
1526
+ }
1527
+ const returnAnnotation = value.returnType?.typeAnnotation;
1528
+ return (!!returnAnnotation &&
1529
+ returnAnnotation.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword);
1530
+ }
1508
1531
  /**
1509
1532
  * Check class property declarations for boolean values.
1510
1533
  *
@@ -1518,6 +1541,38 @@ exports.enforceBooleanNamingPrefixes = (0, createRule_1.createRule)({
1518
1541
  if (!key)
1519
1542
  return;
1520
1543
  const propertyName = key.name;
1544
+ // A field holding a boolean-returning function declares a member that is a
1545
+ // method in every respect its NAME is judged on: callers write
1546
+ // `instance.member()` and read a true/false answer from it, so writing `=`
1547
+ // in front of the member cannot discharge the naming obligation the method
1548
+ // spelling carries. The three real differences between the spellings —
1549
+ // lexical `this`, own-instance placement and initialization order — are all
1550
+ // orthogonal to the name, so the rename remedy is identical.
1551
+ //
1552
+ // Reported before the data-field paths and returned from, so a field that
1553
+ // somehow satisfies both cannot draw two reports on one key.
1554
+ //
1555
+ // Two fields declare a name this site cannot rename: a computed key's
1556
+ // static name belongs to the expression holding it, so renaming `k` in
1557
+ // `[k] = …` renames nothing on the class, and an ambient (`declare`) field
1558
+ // describes a shape provided elsewhere — a base class, a mixin, a
1559
+ // framework — which owns the name.
1560
+ const declaresRenameableName = !node.computed && !node.declare;
1561
+ if (declaresRenameableName &&
1562
+ declaresBooleanReturningFunction(node) &&
1563
+ !hasApprovedPrefix(propertyName)) {
1564
+ context.report({
1565
+ node: node.key,
1566
+ messageId: 'missingBooleanPrefix',
1567
+ data: {
1568
+ type: 'method',
1569
+ name: key.written,
1570
+ capitalizedName: capitalizeFirst(propertyName),
1571
+ prefixes: formatPrefixes(),
1572
+ },
1573
+ });
1574
+ return;
1575
+ }
1521
1576
  // Check if it's a boolean property
1522
1577
  let isBooleanProperty = false;
1523
1578
  // Check if it has a boolean type annotation
@@ -1122,6 +1122,11 @@ function classifyFunctionReturn(fn) {
1122
1122
  ? 'boolean'
1123
1123
  : 'nonBoolean';
1124
1124
  }
1125
+ // A body-less function (`abstract isNotBlank(value?: string);`) with no
1126
+ // return annotation offers no syntactic verdict at all.
1127
+ if (!fn.body) {
1128
+ return 'indeterminate';
1129
+ }
1125
1130
  if (fn.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
1126
1131
  return classifyExpression(fn.body);
1127
1132
  }
@@ -1148,9 +1153,25 @@ function isExemptFromBooleanNaming(fn) {
1148
1153
  function isExemptFunctionValue(node) {
1149
1154
  return (!!node &&
1150
1155
  (node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
1151
- node.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
1156
+ node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
1157
+ // `abstract isNotBlank(value?: string): string | true;` declares the
1158
+ // validator without a body, and must be exempt on the same grounds as
1159
+ // the implementation that satisfies it.
1160
+ node.type === utils_1.AST_NODE_TYPES.TSEmptyBodyFunctionExpression) &&
1152
1161
  isExemptFromBooleanNaming(node));
1153
1162
  }
1163
+ /**
1164
+ * A member declared with a function type but no initializer
1165
+ * (`isNotBlank!: (value?: string) => string | true`) carries its return shape
1166
+ * only in the annotation. Reading it keeps the #1692 validator carve-out from
1167
+ * depending on whether the predicate is declared or implemented in place.
1168
+ */
1169
+ function isExemptFunctionTypeAnnotation(annotation) {
1170
+ const typeNode = annotation?.typeAnnotation;
1171
+ return (typeNode?.type === utils_1.AST_NODE_TYPES.TSFunctionType &&
1172
+ !!typeNode.returnType &&
1173
+ !isBooleanOnlyType(typeNode.returnType.typeAnnotation));
1174
+ }
1154
1175
  exports.enforcePositiveNaming = (0, createRule_1.createRule)({
1155
1176
  name: 'enforce-positive-naming',
1156
1177
  meta: {
@@ -1446,25 +1467,45 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
1446
1467
  }
1447
1468
  }
1448
1469
  /**
1449
- * Check method definitions for negative naming
1470
+ * Check class members methods, fields and their `abstract` forms — for
1471
+ * negative naming. The docs' subject is "class members", and a field is one:
1472
+ * `isNotReady = () => ...` and `isNotReady() { ... }` force a reader through
1473
+ * the same mental inversion, so writing `=` must not silence the rule.
1450
1474
  */
1451
- function checkMethodDefinition(node) {
1475
+ function checkClassMember(node) {
1452
1476
  if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
1453
1477
  return;
1454
- // Only check boolean-returning methods
1478
+ // A computed key references a name bound elsewhere, where the rule
1479
+ // already judges it; reporting here would blame the wrong declaration.
1480
+ if (node.computed)
1481
+ return;
1482
+ // A `declare` field restates the type of a member owned by a base class
1483
+ // or an ambient declaration, so its name is not this class's to choose.
1484
+ if ((node.type === utils_1.AST_NODE_TYPES.PropertyDefinition ||
1485
+ node.type === utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition) &&
1486
+ node.declare) {
1487
+ return;
1488
+ }
1489
+ // Only check boolean-returning members
1455
1490
  if (!isBooleanLike(node.key))
1456
1491
  return;
1457
- // Skip validator predicates returning a non-boolean value.
1492
+ // Skip validator predicates returning a non-boolean value, whether the
1493
+ // shape comes from the value or from a declaration-only annotation.
1458
1494
  if (isExemptFunctionValue(node.value))
1459
1495
  return;
1460
- const methodName = node.key.name;
1461
- const { isNegative, alternatives } = hasBooleanNegativeNaming(methodName);
1496
+ if ((node.type === utils_1.AST_NODE_TYPES.PropertyDefinition ||
1497
+ node.type === utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition) &&
1498
+ isExemptFunctionTypeAnnotation(node.typeAnnotation)) {
1499
+ return;
1500
+ }
1501
+ const memberName = node.key.name;
1502
+ const { isNegative, alternatives } = hasBooleanNegativeNaming(memberName);
1462
1503
  if (isNegative) {
1463
1504
  context.report({
1464
1505
  node: node.key,
1465
1506
  messageId: 'avoidNegativeNaming',
1466
1507
  data: {
1467
- name: methodName,
1508
+ name: memberName,
1468
1509
  alternatives: formatAlternatives(alternatives),
1469
1510
  },
1470
1511
  });
@@ -1562,7 +1603,13 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
1562
1603
  checkFunctionDeclaration(node);
1563
1604
  }
1564
1605
  },
1565
- MethodDefinition: checkMethodDefinition,
1606
+ MethodDefinition: checkClassMember,
1607
+ // A class field is a class member: the property spelling of a method
1608
+ // (`isNotReady = () => ...`) and a plain boolean field
1609
+ // (`isNotReady = false`) are both what the docs promise to cover.
1610
+ PropertyDefinition: checkClassMember,
1611
+ TSAbstractMethodDefinition: checkClassMember,
1612
+ TSAbstractPropertyDefinition: checkClassMember,
1566
1613
  Property: checkProperty,
1567
1614
  TSPropertySignature: checkPropertySignature,
1568
1615
  Identifier(node) {
@@ -4081,6 +4081,14 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4081
4081
  parent.id.type === utils_1.AST_NODE_TYPES.Identifier) {
4082
4082
  return parent.id.name;
4083
4083
  }
4084
+ // A class field holds its name on the member key, so the component
4085
+ // evidence keyed on the name has to reach `Foo = () => <div />` the
4086
+ // same way it reaches `const Foo = () => <div />`.
4087
+ if (parent?.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
4088
+ !parent.computed &&
4089
+ parent.key.type === utils_1.AST_NODE_TYPES.Identifier) {
4090
+ return parent.key.name;
4091
+ }
4084
4092
  }
4085
4093
  return '';
4086
4094
  }
@@ -4100,6 +4108,18 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4100
4108
  }
4101
4109
  }
4102
4110
  }
4111
+ // A class field carries its annotation on the member rather than on a
4112
+ // binding: `Foo: React.FC = () => ...` declares a component exactly as
4113
+ // the `const` spelling above does.
4114
+ const memberParent = node.parent;
4115
+ if (memberParent?.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
4116
+ memberParent.typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
4117
+ const typeText = context.sourceCode.getText(memberParent.typeAnnotation.typeAnnotation);
4118
+ if (/\bReact\.(FC|FunctionComponent)\b/.test(typeText) ||
4119
+ /\b(FC|FunctionComponent)\b/.test(typeText)) {
4120
+ return true;
4121
+ }
4122
+ }
4103
4123
  // Handle FunctionDeclaration/FunctionExpression/ArrowFunction return type: function Foo(): React.JSX.Element { ... }
4104
4124
  if (node.returnType?.type === utils_1.AST_NODE_TYPES.TSTypeAnnotation) {
4105
4125
  const typeText = context.sourceCode.getText(node.returnType.typeAnnotation);
@@ -4114,6 +4134,14 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4114
4134
  * scope analysis, which records JSX element names as references.
4115
4135
  */
4116
4136
  function isUsedAsReactComponent(node, functionName) {
4137
+ // A class field's name is a member, not a lexical binding, so a variable
4138
+ // of the same name found in scope belongs to some other symbol entirely
4139
+ // and says nothing about the field. `<this.Foo />` is a member expression
4140
+ // and records no reference to resolve, so the field relies on the other
4141
+ // component evidence.
4142
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.PropertyDefinition) {
4143
+ return false;
4144
+ }
4117
4145
  const scope = ASTHelpers_1.ASTHelpers.getScope(context, node);
4118
4146
  const variable = ASTHelpers_1.ASTHelpers.findVariableInScope(scope, functionName);
4119
4147
  if (!variable) {
@@ -4205,6 +4233,45 @@ exports.enforceVerbNounNaming = (0, createRule_1.createRule)({
4205
4233
  }
4206
4234
  }
4207
4235
  },
4236
+ /**
4237
+ * A callable class field is the same member as a method with one token
4238
+ * changed — `this.data()` reads identically under either spelling — so
4239
+ * the name answers to the same rule. Writing `=` cannot be a way to opt
4240
+ * out of it. The value gate is what separates the two kinds of field:
4241
+ * only a function-valued one names an action, so `data = 42` stays a
4242
+ * noun-phrased datum, exactly as an assigned variable does.
4243
+ */
4244
+ PropertyDefinition(node) {
4245
+ // A computed key is an expression rather than a name, so there is no
4246
+ // identifier to judge or to rename.
4247
+ if (node.computed)
4248
+ return;
4249
+ if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
4250
+ return;
4251
+ // A `declare` field only restates the type of a member initialized
4252
+ // elsewhere; the declaration that carries the value owns the name.
4253
+ if (node.declare)
4254
+ return;
4255
+ const value = node.value;
4256
+ if (!value ||
4257
+ (value.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
4258
+ value.type !== utils_1.AST_NODE_TYPES.FunctionExpression)) {
4259
+ return;
4260
+ }
4261
+ // A component is a noun by convention, and a field is a routine place
4262
+ // to hold one — the generic-bound render helpers a class exposes are
4263
+ // written this way precisely because they close over `this`.
4264
+ if (isReactComponent(value)) {
4265
+ return;
4266
+ }
4267
+ if (!isVerbPhrase(node.key.name)) {
4268
+ context.report({
4269
+ node: node.key,
4270
+ messageId: 'functionVerbPhrase',
4271
+ data: { name: node.key.name },
4272
+ });
4273
+ }
4274
+ },
4208
4275
  MethodDefinition(node) {
4209
4276
  if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
4210
4277
  return;
@@ -302,6 +302,29 @@ exports.noMisleadingBooleanPrefixes = (0, createRule_1.createRule)({
302
302
  }
303
303
  // If we can't determine it's non-boolean, do not report to avoid false positives
304
304
  }
305
+ /**
306
+ * Judges a class field only when it holds a function literal.
307
+ *
308
+ * Each gate excludes a member that makes no return-value promise, so none of
309
+ * them is conservatism for its own sake: a data field (`isDone = false`,
310
+ * `hasItems = compute()`) is a value rather than a callable contract, a
311
+ * computed key names a variable instead of the member a caller writes, and a
312
+ * `declare`, definite-assignment or abstract field carries no initializer
313
+ * whose returns could be read.
314
+ */
315
+ function checkClassProperty(node) {
316
+ if (node.computed || node.declare)
317
+ return;
318
+ if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
319
+ return;
320
+ const value = node.value;
321
+ if (!value ||
322
+ (value.type !== utils_1.AST_NODE_TYPES.FunctionExpression &&
323
+ value.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
324
+ return;
325
+ }
326
+ checkFunctionLike(value, node.key.name, node.key);
327
+ }
305
328
  return {
306
329
  FunctionDeclaration(node) {
307
330
  if (!node.id)
@@ -320,6 +343,12 @@ exports.noMisleadingBooleanPrefixes = (0, createRule_1.createRule)({
320
343
  return;
321
344
  if (node.parent?.type === utils_1.AST_NODE_TYPES.MethodDefinition)
322
345
  return;
346
+ // A named function expression assigned to a class field carries two
347
+ // names — its own `id` and the field's key — and the field key is the
348
+ // one every call site writes. Without this bail-out the class-member
349
+ // arm below and the `node.id` fallback both fire on the same site.
350
+ if (node.parent?.type === utils_1.AST_NODE_TYPES.PropertyDefinition)
351
+ return;
323
352
  if (node.id) {
324
353
  checkFunctionLike(node, node.id.name, node.id);
325
354
  }
@@ -351,6 +380,20 @@ exports.noMisleadingBooleanPrefixes = (0, createRule_1.createRule)({
351
380
  checkFunctionLike(node.value, node.key.name, node.key);
352
381
  }
353
382
  },
383
+ // A class field holding a function is a function everywhere it matters:
384
+ // `instance.isReady()` reads the same whether the member was written as a
385
+ // method or as `isReady = () => ...`, so the boolean prefix makes the same
386
+ // promise to the same call sites. Keying the class arm on `MethodDefinition`
387
+ // alone let a single `=` silence the rule (#2155), and the bound-property
388
+ // spelling is what an interface demanding a bound member forces.
389
+ //
390
+ // `TSAbstractPropertyDefinition` is registered beside it so the class arm
391
+ // subscribes to every key a field declaration can parse as, matching the
392
+ // inverse boolean-naming rule in the same recommended config. An abstract
393
+ // field parses with no initializer, so the value gate leaves that arm
394
+ // silent — the key is here to keep the two spellings from drifting apart.
395
+ PropertyDefinition: checkClassProperty,
396
+ TSAbstractPropertyDefinition: checkClassProperty,
354
397
  };
355
398
  },
356
399
  });
@@ -815,6 +815,43 @@ exports.noUnnecessaryVerbSuffix = (0, createRule_1.createRule)({
815
815
  checkFunctionName(node.value, node.key.name, null, null, false);
816
816
  }
817
817
  },
818
+ PropertyDefinition(node) {
819
+ // A class field holding a function declares the same callable member a
820
+ // method does — `member = () => {}` and `member() {}` differ by one
821
+ // token and by nothing this rule judges, since it reads only the
822
+ // member's name. Without this arm the `=` spelling silences the rule
823
+ // (#2156), and it is the spelling a class picks whenever a member must
824
+ // stay bound to its instance.
825
+ if (node.computed ||
826
+ // An ambient member declares a type rather than code; its initializer
827
+ // is not valid TypeScript at all (TS1039), so nothing in it is an
828
+ // implementation whose name this rule can hold the author to.
829
+ node.declare ||
830
+ node.key.type !== utils_1.AST_NODE_TYPES.Identifier) {
831
+ return;
832
+ }
833
+ // The value gate is what keeps data fields inert: `cachedFor = new
834
+ // Map()` names a value, not a function, and only a function member is
835
+ // this rule's subject. A field annotated with a function type but
836
+ // holding something else is inert for the same reason.
837
+ const { value } = node;
838
+ if (!value ||
839
+ (value.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
840
+ value.type !== utils_1.AST_NODE_TYPES.FunctionExpression)) {
841
+ return;
842
+ }
843
+ // A member implementing a contract the class declares conformance to
844
+ // is named by that contract, so renaming it would break conformance.
845
+ if (isDictatedByHeritage(node, node.key.name)) {
846
+ return;
847
+ }
848
+ // Report-only for the reason the method arm gives: a field arrow is
849
+ // invoked through `this.x()` / `instance.x()`, member accesses the
850
+ // scope manager does not track as references, so a single-file fixer
851
+ // cannot rename the call sites and must not rename the declaration
852
+ // alone (#1256).
853
+ checkFunctionName(value, node.key.name, null, null, false);
854
+ },
818
855
  TSMethodSignature(node) {
819
856
  // Interface method signatures have their implementations and call sites
820
857
  // elsewhere (member accesses on implementers), unreachable from this
@@ -3,6 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.preferGetterOverParameterlessMethod = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ function isPropertyLike(node) {
7
+ return (node.type === utils_1.AST_NODE_TYPES.PropertyDefinition ||
8
+ node.type === utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition);
9
+ }
6
10
  const DEFAULT_PREFIXES = [
7
11
  'build',
8
12
  'get',
@@ -143,6 +147,50 @@ function isFunctionLikeNode(value) {
143
147
  value.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
144
148
  value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression);
145
149
  }
150
+ /**
151
+ * The member function a class member carries, or null when it carries none. A
152
+ * method's value is always a function; a field's value is one only when it is
153
+ * written as an arrow or a function expression, which is what makes the field a
154
+ * member function rather than data.
155
+ */
156
+ function memberFunctionOf(node) {
157
+ if (!isPropertyLike(node))
158
+ return node.value;
159
+ const value = node.value;
160
+ if (value &&
161
+ (value.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
162
+ value.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
163
+ return value;
164
+ }
165
+ return null;
166
+ }
167
+ /**
168
+ * The return type a FIELD's own annotation declares for the function it holds.
169
+ * `render: () => void = () => this.draw()` states the member's contract beside
170
+ * the field name rather than on the arrow, and that contract is what a caller
171
+ * reads — so it answers the void and promise questions the same way an arrow's
172
+ * own `(): void` annotation does.
173
+ */
174
+ function declaredFunctionReturnType(node) {
175
+ if (!isPropertyLike(node))
176
+ return null;
177
+ const annotation = node.typeAnnotation?.typeAnnotation;
178
+ if (!annotation || annotation.type !== utils_1.AST_NODE_TYPES.TSFunctionType) {
179
+ return null;
180
+ }
181
+ return annotation.returnType?.typeAnnotation ?? null;
182
+ }
183
+ /**
184
+ * The nodes a top-level body walk starts from. A concise arrow body is a single
185
+ * expression rather than a statement list, and every walk here is about what
186
+ * the member's OWN scope does, so the expression is that scope's whole content.
187
+ */
188
+ function bodyWalkRoots(fn) {
189
+ const body = fn.body;
190
+ if (!body)
191
+ return [];
192
+ return body.type === utils_1.AST_NODE_TYPES.BlockStatement ? [...body.body] : [body];
193
+ }
146
194
  function lowerFirst(text) {
147
195
  if (!text)
148
196
  return text;
@@ -167,7 +215,15 @@ function memberNameOf(key, computed) {
167
215
  function isEcmaPrivateName(name) {
168
216
  return name.startsWith('#');
169
217
  }
170
- function computeBodyLineCount(body) {
218
+ function computeBodyLineCount(fn) {
219
+ const body = fn.body;
220
+ if (!body)
221
+ return 0;
222
+ if (body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
223
+ // A concise body has no braces of its own, so its span IS its content: a
224
+ // one-line body counts 0, exactly as the one-line block spelling does.
225
+ return Math.max(0, body.loc.end.line - body.loc.start.line);
226
+ }
171
227
  return Math.max(0, body.loc.end.line - body.loc.start.line - 1);
172
228
  }
173
229
  function hasNameCollision(node, newName) {
@@ -624,8 +680,8 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
624
680
  return (matchesAffirmative(/\bside effects?\b/) ||
625
681
  matchesAffirmative(/\bmutat(?:e|es|ing|ion|ions|ed|ive|ively)?\b/));
626
682
  }
627
- function analyzeMutations(body) {
628
- const stack = [...body.body];
683
+ function analyzeMutations(fn) {
684
+ const stack = bodyWalkRoots(fn);
629
685
  while (stack.length) {
630
686
  const current = stack.pop();
631
687
  if (isFunctionLikeNode(current)) {
@@ -656,17 +712,21 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
656
712
  }
657
713
  return null;
658
714
  }
659
- function returnsValue(node) {
660
- const returnType = node.value.returnType?.typeAnnotation;
715
+ function returnsValue(node, fn) {
716
+ const returnType = fn.returnType?.typeAnnotation ?? declaredFunctionReturnType(node);
661
717
  if (returnType) {
662
718
  if (isVoidishType(returnType)) {
663
719
  return false;
664
720
  }
665
721
  return true;
666
722
  }
667
- const body = node.value.body;
723
+ const body = fn.body;
668
724
  if (!body)
669
725
  return false;
726
+ // A concise arrow body IS the value handed back; the subject visitor has
727
+ // already required it to be one that cannot be read as a command.
728
+ if (body.type !== utils_1.AST_NODE_TYPES.BlockStatement)
729
+ return true;
670
730
  const stack = [...body.body];
671
731
  while (stack.length) {
672
732
  const current = stack.pop();
@@ -886,7 +946,18 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
886
946
  * mentions promises.
887
947
  */
888
948
  function returnsThenable(node) {
889
- return functionYieldsThenable(node, node.value, new Set([node.value]));
949
+ const fn = memberFunctionOf(node);
950
+ if (!fn)
951
+ return false;
952
+ // A field states its contract beside the name when the arrow carries no
953
+ // annotation of its own, and that contract settles the question exactly
954
+ // as an arrow's own annotation would.
955
+ if (!fn.returnType) {
956
+ const declared = declaredFunctionReturnType(node);
957
+ if (declared)
958
+ return isThenableTypeNode(declared);
959
+ }
960
+ return functionYieldsThenable(node, fn, new Set([fn]));
890
961
  }
891
962
  /**
892
963
  * Returns true when the method body contains a ThrowStatement that is
@@ -894,8 +965,8 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
894
965
  * A method that can throw at the top level is an imperative assertion, not
895
966
  * a computed property, so it must not become a getter.
896
967
  */
897
- function containsTopLevelThrow(body) {
898
- const stack = [...body.body];
968
+ function containsTopLevelThrow(fn) {
969
+ const stack = bodyWalkRoots(fn);
899
970
  while (stack.length) {
900
971
  const current = stack.pop();
901
972
  // Do not descend into nested function-like scopes — a throw there is
@@ -937,8 +1008,8 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
937
1008
  }
938
1009
  return false;
939
1010
  }
940
- function bodyReferencesThisProperty(body, propName) {
941
- const stack = [...body.body];
1011
+ function bodyReferencesThisProperty(fn, propName) {
1012
+ const stack = bodyWalkRoots(fn);
942
1013
  while (stack.length) {
943
1014
  const current = stack.pop();
944
1015
  if (isFunctionLikeNode(current)) {
@@ -1019,7 +1090,7 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
1019
1090
  }
1020
1091
  return true;
1021
1092
  }
1022
- function trackMemberReference(member, callUsedNamesByClass, callUsedNamesInFile) {
1093
+ function trackMemberReference(member, callUsedNamesByClass, callUsedNamesInFile, detachedNamesByClass, detachedNamesInFile) {
1023
1094
  const propName = memberNameOf(member.property, member.computed);
1024
1095
  if (propName === null) {
1025
1096
  return;
@@ -1028,22 +1099,29 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
1028
1099
  return;
1029
1100
  }
1030
1101
  addCallUseForMember(member, propName, callUsedNamesByClass, callUsedNamesInFile);
1102
+ addCallUseForMember(member, propName, detachedNamesByClass, detachedNamesInFile);
1031
1103
  }
1032
- function trackThisDestructuring(pattern, init, callUsedNamesByClass, callUsedNamesInFile) {
1104
+ function trackThisDestructuring(pattern, init, callUsedNamesByClass, callUsedNamesInFile, detachedNamesByClass, detachedNamesInFile) {
1033
1105
  if (!init || init.type !== utils_1.AST_NODE_TYPES.ThisExpression) {
1034
1106
  return;
1035
1107
  }
1108
+ const record = (name) => {
1109
+ addCallUse(pattern, name, callUsedNamesByClass, callUsedNamesInFile);
1110
+ // Pulling a member off `this` without calling it takes the function
1111
+ // value itself, which is the detachment a getter cannot survive.
1112
+ addCallUse(pattern, name, detachedNamesByClass, detachedNamesInFile);
1113
+ };
1036
1114
  for (const prop of pattern.properties) {
1037
1115
  if (prop.type === utils_1.AST_NODE_TYPES.Property) {
1038
1116
  if (prop.computed)
1039
1117
  continue;
1040
1118
  const key = prop.key;
1041
1119
  if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
1042
- addCallUse(pattern, key.name, callUsedNamesByClass, callUsedNamesInFile);
1120
+ record(key.name);
1043
1121
  }
1044
1122
  else if (key.type === utils_1.AST_NODE_TYPES.Literal &&
1045
1123
  typeof key.value === 'string') {
1046
- addCallUse(pattern, key.value, callUsedNamesByClass, callUsedNamesInFile);
1124
+ record(key.value);
1047
1125
  }
1048
1126
  }
1049
1127
  }
@@ -1087,7 +1165,82 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
1087
1165
  }
1088
1166
  const callUsedNamesByClass = new WeakMap();
1089
1167
  const callUsedNamesInFile = new Set();
1168
+ /**
1169
+ * Names read WITHOUT being called — the strict subset of the call-use sets
1170
+ * that means "the function value itself was taken". A field arrow exists to
1171
+ * be handed around bound (`store.on('change', this.getSnapshot)`), and a
1172
+ * getter would run on that read instead of yielding the function, so the
1173
+ * field arm withholds its report there rather than prescribe a remedy that
1174
+ * breaks the site.
1175
+ */
1176
+ const detachedNamesByClass = new WeakMap();
1177
+ const detachedNamesInFile = new Set();
1090
1178
  const candidates = [];
1179
+ /**
1180
+ * The gates every member function answers, whichever way it is spelled.
1181
+ */
1182
+ function considerMember(node, fn, isField) {
1183
+ if (fn.params.length > 0)
1184
+ return;
1185
+ if (node.optional)
1186
+ return;
1187
+ if (node.computed)
1188
+ return;
1189
+ if (fn.typeParameters)
1190
+ return;
1191
+ // An ECMA private name (`#foo`) is the same privacy as the TypeScript
1192
+ // `private` modifier — and mutually exclusive with it, since `private
1193
+ // #foo` is TS18010 — so it carries a plain, strippable name that the
1194
+ // rule analyzes exactly like an `Identifier` key. Literal, computed and
1195
+ // template keys stay out: their names are not always statically known
1196
+ // and the emitted getter would need quoting the fixer does not do.
1197
+ if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier &&
1198
+ node.key.type !== utils_1.AST_NODE_TYPES.PrivateIdentifier) {
1199
+ return;
1200
+ }
1201
+ // `ignoreAsync` means "ignore asynchronous members", not "ignore members
1202
+ // bearing the async keyword": TypeScript does not require the keyword to
1203
+ // return a promise, so `fetchToken(): Promise<string>` is asynchronous
1204
+ // with no keyword written at all (#2154).
1205
+ if (config.ignoreAsync && returnsThenable(node))
1206
+ return;
1207
+ if (node.override)
1208
+ return;
1209
+ if (!fn.body)
1210
+ return;
1211
+ if (isConstrainedByHeritage(node))
1212
+ return;
1213
+ const name = node.key.name;
1214
+ if (ignoredMethods.has(name))
1215
+ return;
1216
+ // Factory/builder terminal members are imperative actions (issue #990 #4).
1217
+ // Exemption takes precedence over stripPrefixes so names like "build"
1218
+ // are never prefix-stripped into a getter candidate.
1219
+ if (factoryMethods.has(name))
1220
+ return;
1221
+ if (computeBodyLineCount(fn) < config.minBodyLines) {
1222
+ return;
1223
+ }
1224
+ if (!returnsValue(node, fn))
1225
+ return;
1226
+ if (config.respectJsDocSideEffects && hasSideEffectTag(node))
1227
+ return;
1228
+ // A member that throws at the top level is an imperative assertion or
1229
+ // command, not a computed property — getters must be pure/non-throwing.
1230
+ if (containsTopLevelThrow(fn))
1231
+ return;
1232
+ const sideEffectReason = analyzeMutations(fn);
1233
+ const suggestedName = suggestName(name);
1234
+ const sigil = node.key.type === utils_1.AST_NODE_TYPES.PrivateIdentifier ? '#' : '';
1235
+ candidates.push({
1236
+ node,
1237
+ fn,
1238
+ sideEffectReason,
1239
+ memberName: `${sigil}${name}`,
1240
+ getterName: `${sigil}${suggestedName}`,
1241
+ isField,
1242
+ });
1243
+ }
1091
1244
  return {
1092
1245
  CallExpression(node) {
1093
1246
  const callee = node.callee;
@@ -1102,21 +1255,31 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
1102
1255
  }
1103
1256
  },
1104
1257
  MemberExpression(node) {
1105
- trackMemberReference(node, callUsedNamesByClass, callUsedNamesInFile);
1258
+ trackMemberReference(node, callUsedNamesByClass, callUsedNamesInFile, detachedNamesByClass, detachedNamesInFile);
1106
1259
  },
1107
1260
  ChainExpression(node) {
1108
1261
  if (node.expression.type === utils_1.AST_NODE_TYPES.MemberExpression) {
1109
- trackMemberReference(node.expression, callUsedNamesByClass, callUsedNamesInFile);
1262
+ trackMemberReference(node.expression, callUsedNamesByClass, callUsedNamesInFile, detachedNamesByClass, detachedNamesInFile);
1110
1263
  }
1111
1264
  },
1112
1265
  VariableDeclarator(node) {
1113
1266
  if (node.id.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
1114
- trackThisDestructuring(node.id, node.init ?? null, callUsedNamesByClass, callUsedNamesInFile);
1267
+ trackThisDestructuring(node.id, node.init ?? null, callUsedNamesByClass, callUsedNamesInFile, detachedNamesByClass, detachedNamesInFile);
1115
1268
  }
1116
1269
  },
1117
1270
  AssignmentExpression(node) {
1118
1271
  if (node.left.type === utils_1.AST_NODE_TYPES.ObjectPattern) {
1119
- trackThisDestructuring(node.left, node.right, callUsedNamesByClass, callUsedNamesInFile);
1272
+ trackThisDestructuring(node.left, node.right, callUsedNamesByClass, callUsedNamesInFile, detachedNamesByClass, detachedNamesInFile);
1273
+ }
1274
+ },
1275
+ JSXMemberExpression(node) {
1276
+ // `<this.Panel />` takes the member's function value — JSX invokes the
1277
+ // component itself — but it is a JSXMemberExpression, which the member
1278
+ // trackers never see. A component declared as a field is exactly the
1279
+ // shape a getter would break, so this counts as detachment.
1280
+ const property = node.property;
1281
+ if (property.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
1282
+ addCallUse(node, property.name, detachedNamesByClass, detachedNamesInFile);
1120
1283
  }
1121
1284
  },
1122
1285
  BinaryExpression(node) {
@@ -1133,73 +1296,37 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
1133
1296
  'MethodDefinition, TSAbstractMethodDefinition'(node) {
1134
1297
  if (node.kind !== 'method')
1135
1298
  return;
1136
- if (node.value.params.length > 0)
1137
- return;
1138
1299
  if (node.value.generator)
1139
1300
  return;
1140
- if (node.optional)
1141
- return;
1142
- if (node.computed)
1143
- return;
1144
- if (node.value.typeParameters)
1145
- return;
1146
- // An ECMA private name (`#foo`) is the same privacy as the TypeScript
1147
- // `private` modifier — and mutually exclusive with it, since `private
1148
- // #foo` is TS18010 — so it carries a plain, strippable name that the
1149
- // rule analyzes exactly like an `Identifier` key. Literal, computed and
1150
- // template keys stay out: their names are not always statically known
1151
- // and the emitted getter would need quoting the fixer does not do.
1152
- if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier &&
1153
- node.key.type !== utils_1.AST_NODE_TYPES.PrivateIdentifier) {
1154
- return;
1155
- }
1156
- // `ignoreAsync` means "ignore asynchronous methods", not "ignore
1157
- // methods bearing the async keyword": TypeScript does not require the
1158
- // keyword to return a promise, so `fetchToken(): Promise<string>` is
1159
- // asynchronous with no keyword written at all (#2154).
1160
- if (config.ignoreAsync && returnsThenable(node))
1161
- return;
1162
1301
  if (config.ignoreAbstract &&
1163
1302
  node.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition) {
1164
1303
  return;
1165
1304
  }
1166
- if (node.override)
1167
- return;
1168
- if (!node.value.body)
1169
- return;
1170
- if (isConstrainedByHeritage(node))
1171
- return;
1172
- const name = node.key.name;
1173
- if (ignoredMethods.has(name))
1174
- return;
1175
- // Factory/builder terminal methods are imperative actions (issue #990 #4).
1176
- // Exemption takes precedence over stripPrefixes so names like "build"
1177
- // are never prefix-stripped into a getter candidate.
1178
- if (factoryMethods.has(name))
1179
- return;
1305
+ // Getters cannot carry overload declarations, so leaving the signatures
1306
+ // in place would produce invalid TypeScript. Only a method can have
1307
+ // them: a field and an overload signature of the same name is not a
1308
+ // legal class body.
1180
1309
  if (hasOverloadSignatures(node))
1181
1310
  return;
1182
- const body = node.value.body;
1183
- if (computeBodyLineCount(body) < config.minBodyLines) {
1184
- return;
1185
- }
1186
- if (!returnsValue(node))
1311
+ considerMember(node, node.value, false);
1312
+ },
1313
+ 'PropertyDefinition, TSAbstractPropertyDefinition'(node) {
1314
+ // A field is a member function only when it holds one. Data fields,
1315
+ // `declare` fields and `abstract` declarations hold no function at all,
1316
+ // so they leave here rather than needing a gate of their own.
1317
+ const fn = memberFunctionOf(node);
1318
+ if (!fn)
1187
1319
  return;
1188
- if (config.respectJsDocSideEffects && hasSideEffectTag(node))
1320
+ // `foo = function* () {}` hands back an iterator through a protocol a
1321
+ // getter has no way to express.
1322
+ if (fn.type === utils_1.AST_NODE_TYPES.FunctionExpression && fn.generator) {
1189
1323
  return;
1190
- // A method that throws at the top level is an imperative assertion or
1191
- // command, not a computed property — getters must be pure/non-throwing.
1192
- if (containsTopLevelThrow(body))
1324
+ }
1325
+ if (config.ignoreAbstract &&
1326
+ node.type === utils_1.AST_NODE_TYPES.TSAbstractPropertyDefinition) {
1193
1327
  return;
1194
- const sideEffectReason = analyzeMutations(body);
1195
- const suggestedName = suggestName(name);
1196
- const sigil = node.key.type === utils_1.AST_NODE_TYPES.PrivateIdentifier ? '#' : '';
1197
- candidates.push({
1198
- node,
1199
- sideEffectReason,
1200
- memberName: `${sigil}${name}`,
1201
- getterName: `${sigil}${suggestedName}`,
1202
- });
1328
+ }
1329
+ considerMember(node, fn, true);
1203
1330
  },
1204
1331
  'Program:exit'() {
1205
1332
  const suggestedNameCounts = new WeakMap();
@@ -1212,7 +1339,28 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
1212
1339
  const side = node.static ?? false;
1213
1340
  return `${side ? 'static' : 'instance'}:${getterName}`;
1214
1341
  };
1215
- for (const { node, getterName } of candidates) {
1342
+ /**
1343
+ * A field handed around as a function value is the one shape whose
1344
+ * getter remedy is wrong rather than merely unfixable: the caller that
1345
+ * wrote `this.getSnapshot` wants the function, and a getter would run
1346
+ * the body and hand back its result instead. The method arm keeps
1347
+ * reporting such members — its remedy is to convert the member AND its
1348
+ * call sites, which stays available — so this withholding is specific
1349
+ * to the spelling whose purpose is detachment.
1350
+ */
1351
+ const isDetachedField = (candidate) => {
1352
+ if (!candidate.isField)
1353
+ return false;
1354
+ const classBody = candidate.node.parent;
1355
+ const withinClass = classBody?.type === utils_1.AST_NODE_TYPES.ClassBody
1356
+ ? detachedNamesByClass
1357
+ .get(classBody)
1358
+ ?.has(candidate.memberName) ?? false
1359
+ : false;
1360
+ return withinClass || detachedNamesInFile.has(candidate.memberName);
1361
+ };
1362
+ const reportable = candidates.filter((candidate) => !isDetachedField(candidate));
1363
+ for (const { node, getterName } of reportable) {
1216
1364
  const classBody = node.parent;
1217
1365
  if (!classBody || classBody.type !== utils_1.AST_NODE_TYPES.ClassBody) {
1218
1366
  continue;
@@ -1222,7 +1370,7 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
1222
1370
  existingCounts.set(scopeKey, (existingCounts.get(scopeKey) ?? 0) + 1);
1223
1371
  suggestedNameCounts.set(classBody, existingCounts);
1224
1372
  }
1225
- for (const { node, sideEffectReason, memberName, getterName, } of candidates) {
1373
+ for (const { node, fn, sideEffectReason, memberName, getterName, isField, } of reportable) {
1226
1374
  const classBody = node.parent;
1227
1375
  const scopeKey = scopeKeyOf(node, getterName);
1228
1376
  const leftParen = sourceCode.getTokenAfter(node.key, {
@@ -1233,10 +1381,7 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
1233
1381
  filter: (token) => token.value === ')',
1234
1382
  })
1235
1383
  : null;
1236
- const body = node.value.body;
1237
- const referencesSuggestedName = body && body.type === utils_1.AST_NODE_TYPES.BlockStatement
1238
- ? bodyReferencesThisProperty(body, getterName)
1239
- : false;
1384
+ const referencesSuggestedName = bodyReferencesThisProperty(fn, getterName);
1240
1385
  const hasCollision = hasNameCollision(node, getterName);
1241
1386
  const isCallUsed = classBody?.type === utils_1.AST_NODE_TYPES.ClassBody
1242
1387
  ? callUsedNamesByClass.get(classBody)?.has(memberName) ?? false
@@ -1281,7 +1426,16 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
1281
1426
  suggestedName: getterName,
1282
1427
  reason: sideEffectReason ?? 'it returns a value',
1283
1428
  },
1284
- fix: !isPrivate ||
1429
+ fix:
1430
+ // The field spelling is report-only. Its rewrite must consume the
1431
+ // `= (`…`) =>` and the terminating `;`, reshape a concise body
1432
+ // into a block, and drop a `readonly` modifier a getter cannot
1433
+ // carry — and even done perfectly it turns an own enumerable
1434
+ // per-instance property into a prototype accessor, which changes
1435
+ // `Object.keys(instance)` and object spread. No gate here models
1436
+ // that, so the developer applies it.
1437
+ isField ||
1438
+ !isPrivate ||
1285
1439
  sideEffectReason ||
1286
1440
  isThenableReturning ||
1287
1441
  !leftParen ||
@@ -171,6 +171,49 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
171
171
  privateIdentifierPrefix: '#',
172
172
  }));
173
173
  }
174
+ /**
175
+ * A class member written as a function-valued field (`getUser = () => {}`)
176
+ * declares the same thing as `getUser() {}`: a named, callable member of the
177
+ * class. The two spellings differ only in binding and `this` semantics, not
178
+ * in what this rule judges, so a single `=` must not decide whether the name
179
+ * is read.
180
+ */
181
+ function checkPropertyName(node) {
182
+ const value = node.value;
183
+ /**
184
+ * Only a function-valued field names an operation. `updateCount = 0` and
185
+ * `getters = {}` are data whose names describe a value, not a verb applied
186
+ * to one, and a field with no initializer (`declare getData: Fn`,
187
+ * `getData!: Fn`) declares no implementation to name.
188
+ */
189
+ if (value?.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
190
+ value?.type !== utils_1.AST_NODE_TYPES.FunctionExpression) {
191
+ return;
192
+ }
193
+ /**
194
+ * A named function expression carries its own binding, which the function
195
+ * arm reads and reports on. Deferring to it keeps exactly one report per
196
+ * site and matches the precedence the rule already applies to the variable
197
+ * spelling, where `const getUser = function getData() {}` is reported as
198
+ * `getData`.
199
+ */
200
+ if (value.type === utils_1.AST_NODE_TYPES.FunctionExpression && value.id) {
201
+ return;
202
+ }
203
+ const { key } = node;
204
+ // Same readable-key requirement as the method arm; see checkMethodName.
205
+ if (node.computed ||
206
+ (key.type !== utils_1.AST_NODE_TYPES.Identifier &&
207
+ key.type !== utils_1.AST_NODE_TYPES.PrivateIdentifier)) {
208
+ return;
209
+ }
210
+ const propertyName = key.name;
211
+ if (!propertyName)
212
+ return;
213
+ reportIfGenericPrefix(key, propertyName, (0, getMethodName_1.getMethodName)(node, context.getSourceCode(), {
214
+ privateIdentifierPrefix: '#',
215
+ }));
216
+ }
174
217
  function checkFunctionName(node) {
175
218
  // Skip anonymous functions
176
219
  if (!node.id && node.parent?.type !== utils_1.AST_NODE_TYPES.VariableDeclarator) {
@@ -194,6 +237,7 @@ exports.semanticFunctionPrefixes = (0, createRule_1.createRule)({
194
237
  FunctionExpression: checkFunctionName,
195
238
  ArrowFunctionExpression: checkFunctionName,
196
239
  MethodDefinition: checkMethodName,
240
+ PropertyDefinition: checkPropertyName,
197
241
  };
198
242
  },
199
243
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.181",
3
+ "version": "1.20.182",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,74 @@
1
1
  [
2
+ {
3
+ "version": "1.20.182",
4
+ "date": "2026-08-27T12:12:45.593Z",
5
+ "rules": [
6
+ {
7
+ "name": "enforce-assert-throws",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 2162
11
+ ],
12
+ "summary": "name a function-valued class field (closes #2162)"
13
+ },
14
+ {
15
+ "name": "enforce-boolean-naming-prefixes",
16
+ "changeType": "fix",
17
+ "issues": [
18
+ 2159
19
+ ],
20
+ "summary": "read a field's boolean-returning function value (closes #2159)"
21
+ },
22
+ {
23
+ "name": "enforce-positive-naming",
24
+ "changeType": "fix",
25
+ "issues": [
26
+ 2157
27
+ ],
28
+ "summary": "judge class fields and abstract members (closes #2157)"
29
+ },
30
+ {
31
+ "name": "enforce-verb-noun-naming",
32
+ "changeType": "fix",
33
+ "issues": [
34
+ 2160
35
+ ],
36
+ "summary": "check a callable class field (closes #2160)"
37
+ },
38
+ {
39
+ "name": "no-misleading-boolean-prefixes",
40
+ "changeType": "fix",
41
+ "issues": [
42
+ 2155
43
+ ],
44
+ "summary": "judge a boolean-prefixed class field holding a function (closes #2155)"
45
+ },
46
+ {
47
+ "name": "no-unnecessary-verb-suffix",
48
+ "changeType": "fix",
49
+ "issues": [
50
+ 2156
51
+ ],
52
+ "summary": "check a class field holding a function (closes #2156)"
53
+ },
54
+ {
55
+ "name": "prefer-getter-over-parameterless-method",
56
+ "changeType": "fix",
57
+ "issues": [
58
+ 2158
59
+ ],
60
+ "summary": "consider a function-valued class field (closes #2158)"
61
+ },
62
+ {
63
+ "name": "semantic-function-prefixes",
64
+ "changeType": "fix",
65
+ "issues": [
66
+ 2161
67
+ ],
68
+ "summary": "judge a function-valued class property (closes #2161)"
69
+ }
70
+ ]
71
+ },
2
72
  {
3
73
  "version": "1.20.181",
4
74
  "date": "2026-08-27T06:29:43.247Z",