@blumintinc/eslint-plugin-blumint 1.20.180 → 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 +1 -1
- package/lib/rules/enforce-assert-throws.js +27 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +55 -0
- package/lib/rules/enforce-positive-naming.js +56 -9
- package/lib/rules/enforce-verb-noun-naming.js +67 -0
- package/lib/rules/no-misleading-boolean-prefixes.js +43 -0
- package/lib/rules/no-unnecessary-verb-suffix.js +37 -0
- package/lib/rules/prefer-getter-over-parameterless-method.js +515 -79
- package/lib/rules/semantic-function-prefixes.js +44 -0
- package/package.json +1 -1
- package/release-manifest.json +84 -0
package/lib/index.js
CHANGED
|
@@ -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
|
|
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
|
|
1475
|
+
function checkClassMember(node) {
|
|
1452
1476
|
if (node.key.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
1453
1477
|
return;
|
|
1454
|
-
//
|
|
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
|
-
|
|
1461
|
-
|
|
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:
|
|
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:
|
|
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
|