@blumintinc/eslint-plugin-blumint 1.19.30 → 1.19.32
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-positive-naming.js +140 -0
- package/package.json +1 -1
- package/release-manifest.json +28 -0
package/lib/index.js
CHANGED
|
@@ -921,6 +921,130 @@ const BOOLEAN_POSITIVE_ALTERNATIVES = {
|
|
|
921
921
|
WILL_NOT: ['WILL'],
|
|
922
922
|
DOES_NOT: ['DOES'],
|
|
923
923
|
};
|
|
924
|
+
/**
|
|
925
|
+
* Recognizes type nodes that denote a boolean-only value: the `boolean`
|
|
926
|
+
* keyword, a `true`/`false` literal type, or a union whose every arm is
|
|
927
|
+
* boolean. A validator's `string | true` return type is deliberately NOT
|
|
928
|
+
* boolean-only, so a predicate annotated that way is not treated as a boolean.
|
|
929
|
+
*/
|
|
930
|
+
function isBooleanOnlyType(typeNode) {
|
|
931
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSBooleanKeyword) {
|
|
932
|
+
return true;
|
|
933
|
+
}
|
|
934
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSLiteralType &&
|
|
935
|
+
typeNode.literal.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
936
|
+
typeof typeNode.literal.value === 'boolean') {
|
|
937
|
+
return true;
|
|
938
|
+
}
|
|
939
|
+
if (typeNode.type === utils_1.AST_NODE_TYPES.TSUnionType) {
|
|
940
|
+
return typeNode.types.every(isBooleanOnlyType);
|
|
941
|
+
}
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Detects an expression that is definitively non-boolean — a string or number
|
|
946
|
+
* literal, or a template/object/array literal. Validator predicates return an
|
|
947
|
+
* error-message string on rejection (and `true` on success), so a string
|
|
948
|
+
* literal return is the syntactic tell that an `is`/`has`-prefixed value is not
|
|
949
|
+
* actually a boolean. Ambiguous expressions (calls, identifiers, comparisons,
|
|
950
|
+
* negations) are intentionally NOT treated as non-boolean, preserving the
|
|
951
|
+
* rule's existing flagging of genuine negatively-named booleans.
|
|
952
|
+
*/
|
|
953
|
+
function isDefinitelyNonBooleanExpression(node) {
|
|
954
|
+
switch (node.type) {
|
|
955
|
+
case utils_1.AST_NODE_TYPES.Literal:
|
|
956
|
+
return typeof node.value === 'string' || typeof node.value === 'number';
|
|
957
|
+
case utils_1.AST_NODE_TYPES.TemplateLiteral:
|
|
958
|
+
case utils_1.AST_NODE_TYPES.ObjectExpression:
|
|
959
|
+
case utils_1.AST_NODE_TYPES.ArrayExpression:
|
|
960
|
+
return true;
|
|
961
|
+
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
962
|
+
return (isDefinitelyNonBooleanExpression(node.consequent) ||
|
|
963
|
+
isDefinitelyNonBooleanExpression(node.alternate));
|
|
964
|
+
// Unwrap type-only expression wrappers (`'msg' as const`, `x satisfies T`,
|
|
965
|
+
// `x!`) so an asserted string/number literal is still recognized. Unwrapping
|
|
966
|
+
// can only expose a non-boolean literal, never mask a real boolean, so it
|
|
967
|
+
// cannot over-exempt a genuinely negatively-named boolean.
|
|
968
|
+
case utils_1.AST_NODE_TYPES.TSAsExpression:
|
|
969
|
+
case utils_1.AST_NODE_TYPES.TSSatisfiesExpression:
|
|
970
|
+
case utils_1.AST_NODE_TYPES.TSNonNullExpression:
|
|
971
|
+
return isDefinitelyNonBooleanExpression(node.expression);
|
|
972
|
+
default:
|
|
973
|
+
return false;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Yields the immediate AST-node children of `node`, skipping the `parent`
|
|
978
|
+
* back-reference so traversal only walks downward.
|
|
979
|
+
*/
|
|
980
|
+
function childNodesOf(node) {
|
|
981
|
+
const children = [];
|
|
982
|
+
for (const key of Object.keys(node)) {
|
|
983
|
+
if (key === 'parent')
|
|
984
|
+
continue;
|
|
985
|
+
const value = node[key];
|
|
986
|
+
const candidates = Array.isArray(value) ? value : [value];
|
|
987
|
+
for (const candidate of candidates) {
|
|
988
|
+
if (candidate &&
|
|
989
|
+
typeof candidate === 'object' &&
|
|
990
|
+
typeof candidate.type === 'string') {
|
|
991
|
+
children.push(candidate);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return children;
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Whether any `return` statement belonging to `fn`'s own body (not a nested
|
|
999
|
+
* function's) yields a definitively non-boolean value.
|
|
1000
|
+
*/
|
|
1001
|
+
function blockReturnsNonBoolean(block) {
|
|
1002
|
+
const stack = [block];
|
|
1003
|
+
while (stack.length > 0) {
|
|
1004
|
+
const current = stack.pop();
|
|
1005
|
+
// Do not descend into nested function scopes: their returns belong to them.
|
|
1006
|
+
if (current !== block &&
|
|
1007
|
+
(current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
1008
|
+
current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
1009
|
+
current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
if (current.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
1013
|
+
current.argument &&
|
|
1014
|
+
isDefinitelyNonBooleanExpression(current.argument)) {
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
for (const child of childNodesOf(current)) {
|
|
1018
|
+
stack.push(child);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return false;
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Whether a function backing an `is`/`has`-prefixed name actually returns a
|
|
1025
|
+
* non-boolean value — e.g. a validator predicate returning `string | true`. An
|
|
1026
|
+
* explicit return-type annotation is authoritative; otherwise the body's own
|
|
1027
|
+
* `return` statements (or the concise-arrow expression) are inspected.
|
|
1028
|
+
*/
|
|
1029
|
+
function functionReturnsNonBoolean(fn) {
|
|
1030
|
+
if (fn.returnType) {
|
|
1031
|
+
return !isBooleanOnlyType(fn.returnType.typeAnnotation);
|
|
1032
|
+
}
|
|
1033
|
+
if (fn.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
1034
|
+
return isDefinitelyNonBooleanExpression(fn.body);
|
|
1035
|
+
}
|
|
1036
|
+
return blockReturnsNonBoolean(fn.body);
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* When a declarator/property value is a function, whether that function is a
|
|
1040
|
+
* non-boolean predicate that must be exempt from boolean negative-naming.
|
|
1041
|
+
*/
|
|
1042
|
+
function isNonBooleanFunctionValue(node) {
|
|
1043
|
+
return (!!node &&
|
|
1044
|
+
(node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
1045
|
+
node.type === utils_1.AST_NODE_TYPES.FunctionExpression) &&
|
|
1046
|
+
functionReturnsNonBoolean(node));
|
|
1047
|
+
}
|
|
924
1048
|
exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
925
1049
|
name: 'enforce-positive-naming',
|
|
926
1050
|
meta: {
|
|
@@ -1152,6 +1276,12 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
1152
1276
|
// Only check boolean-like variables
|
|
1153
1277
|
if (!isBooleanLike(node.id) && !isBooleanLike(node))
|
|
1154
1278
|
return;
|
|
1279
|
+
// A validator predicate (e.g. returns `string | true`) is name-prefixed
|
|
1280
|
+
// with `is`/`has` but is not a boolean, so its domain-correct negation
|
|
1281
|
+
// ("isNotBlank") must not be flagged. The name heuristic alone cannot
|
|
1282
|
+
// tell them apart; the initializer's return shape can.
|
|
1283
|
+
if (isNonBooleanFunctionValue(node.init))
|
|
1284
|
+
return;
|
|
1155
1285
|
const variableName = node.id.name;
|
|
1156
1286
|
const { isNegative, alternatives } = hasBooleanNegativeNaming(variableName);
|
|
1157
1287
|
if (isNegative) {
|
|
@@ -1192,6 +1322,10 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
1192
1322
|
// Only check boolean-returning functions
|
|
1193
1323
|
if (!isBooleanLike(node.id || node))
|
|
1194
1324
|
return;
|
|
1325
|
+
// Skip validator predicates that return a non-boolean value (e.g.
|
|
1326
|
+
// `string | true`), whose negation is the domain-correct term.
|
|
1327
|
+
if (functionReturnsNonBoolean(node))
|
|
1328
|
+
return;
|
|
1195
1329
|
const { isNegative, alternatives } = hasBooleanNegativeNaming(functionName);
|
|
1196
1330
|
if (isNegative) {
|
|
1197
1331
|
context.report({
|
|
@@ -1213,6 +1347,9 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
1213
1347
|
// Only check boolean-returning methods
|
|
1214
1348
|
if (!isBooleanLike(node.key))
|
|
1215
1349
|
return;
|
|
1350
|
+
// Skip validator predicates returning a non-boolean value.
|
|
1351
|
+
if (isNonBooleanFunctionValue(node.value))
|
|
1352
|
+
return;
|
|
1216
1353
|
const methodName = node.key.name;
|
|
1217
1354
|
const { isNegative, alternatives } = hasBooleanNegativeNaming(methodName);
|
|
1218
1355
|
if (isNegative) {
|
|
@@ -1235,6 +1372,9 @@ exports.enforcePositiveNaming = (0, createRule_1.createRule)({
|
|
|
1235
1372
|
// Only check boolean properties
|
|
1236
1373
|
if (!isBooleanLike(node.key))
|
|
1237
1374
|
return;
|
|
1375
|
+
// Skip validator predicates returning a non-boolean value.
|
|
1376
|
+
if (isNonBooleanFunctionValue(node.value))
|
|
1377
|
+
return;
|
|
1238
1378
|
const propertyName = node.key.name;
|
|
1239
1379
|
const { isNegative, alternatives } = hasBooleanNegativeNaming(propertyName);
|
|
1240
1380
|
if (isNegative) {
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.19.32",
|
|
4
|
+
"date": "2026-07-24T00:36:05.744Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-positive-naming",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1344
|
|
11
|
+
],
|
|
12
|
+
"summary": "recognize wrapped and annotated non-boolean validator returns"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.19.31",
|
|
18
|
+
"date": "2026-07-24T00:26:32.378Z",
|
|
19
|
+
"rules": [
|
|
20
|
+
{
|
|
21
|
+
"name": "enforce-positive-naming",
|
|
22
|
+
"changeType": "fix",
|
|
23
|
+
"issues": [
|
|
24
|
+
1344
|
|
25
|
+
],
|
|
26
|
+
"summary": "don't flag non-boolean validator predicates (closes #1344)"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
2
30
|
{
|
|
3
31
|
"version": "1.19.30",
|
|
4
32
|
"date": "2026-07-23T22:28:34.611Z",
|