@blumintinc/eslint-plugin-blumint 1.20.148 → 1.20.150
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-exported-function-types.js +0 -29
- package/lib/rules/enforce-querykey-ts.js +46 -4
- package/lib/rules/no-array-length-in-deps.js +37 -1
- package/lib/rules/no-type-assertion-returns.js +0 -4
- package/lib/rules/prefer-global-router-state-key.js +46 -5
- package/lib/utils/hungarianNaming.d.ts +22 -0
- package/lib/utils/hungarianNaming.js +130 -0
- package/package.json +1 -1
- package/release-manifest.json +53 -0
package/lib/index.js
CHANGED
|
@@ -748,35 +748,6 @@ exports.enforceExportedFunctionTypes = (0, createRule_1.createRule)({
|
|
|
748
748
|
return;
|
|
749
749
|
checkComponentProps(component.fn);
|
|
750
750
|
},
|
|
751
|
-
// Skip type checking for React components since we handle them separately
|
|
752
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > TSTypeAnnotation'() { },
|
|
753
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > TSTypeAnnotation > TSTypeReference'() { },
|
|
754
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > TSTypeAnnotation'() { },
|
|
755
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > TSTypeAnnotation > TSTypeReference'() { },
|
|
756
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation'() { },
|
|
757
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation'() { },
|
|
758
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference'() { },
|
|
759
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference'() { },
|
|
760
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > Identifier'() { },
|
|
761
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > Identifier'() { },
|
|
762
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName'() { },
|
|
763
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName'() { },
|
|
764
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > Identifier'() { },
|
|
765
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > Identifier'() { },
|
|
766
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName'() { },
|
|
767
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName'() { },
|
|
768
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > Identifier'() { },
|
|
769
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > Identifier'() { },
|
|
770
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
|
|
771
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
|
|
772
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
|
|
773
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
|
|
774
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
|
|
775
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
|
|
776
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
|
|
777
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > Identifier'() { },
|
|
778
|
-
'FunctionDeclaration[id.name=/^[A-Z]/] > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
|
|
779
|
-
'VariableDeclarator[id.name=/^[A-Z]/] > ArrowFunctionExpression > Identifier[typeAnnotation] > TSTypeAnnotation > TSTypeReference > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName > TSQualifiedName'() { },
|
|
780
751
|
};
|
|
781
752
|
},
|
|
782
753
|
});
|
|
@@ -385,6 +385,16 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
385
385
|
* Track variable assignments to detect variables derived from query key constants
|
|
386
386
|
*/
|
|
387
387
|
const variableAssignments = new Map();
|
|
388
|
+
/**
|
|
389
|
+
* Names whose declaration leaves them holding `undefined` — `let key;` and
|
|
390
|
+
* `let key = undefined;` alike. Both falsy and nullish, which is what makes
|
|
391
|
+
* a later `||=`/`??=` onto one of them assign unconditionally.
|
|
392
|
+
*/
|
|
393
|
+
const declaredUndefined = new Set();
|
|
394
|
+
/** Declarations and assignments seen per name, to bound the above to the provable case. */
|
|
395
|
+
const declarationCounts = new Map();
|
|
396
|
+
const assignmentCounts = new Map();
|
|
397
|
+
const isUndefinedIdentifier = (node) => node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === 'undefined';
|
|
388
398
|
/**
|
|
389
399
|
* Check if a node represents a valid query key usage
|
|
390
400
|
*/
|
|
@@ -631,13 +641,45 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
|
|
|
631
641
|
},
|
|
632
642
|
// Track variable declarations that might derive from query key constants
|
|
633
643
|
VariableDeclarator(node) {
|
|
634
|
-
if (node.id.type
|
|
635
|
-
|
|
644
|
+
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
645
|
+
return;
|
|
646
|
+
const { name } = node.id;
|
|
647
|
+
declarationCounts.set(name, (declarationCounts.get(name) ?? 0) + 1);
|
|
648
|
+
if (node.init) {
|
|
649
|
+
variableAssignments.set(name, node.init);
|
|
650
|
+
}
|
|
651
|
+
if (!node.init || isUndefinedIdentifier(node.init)) {
|
|
652
|
+
declaredUndefined.add(name);
|
|
636
653
|
}
|
|
637
654
|
},
|
|
638
655
|
AssignmentExpression(node) {
|
|
639
|
-
if (node.left.type
|
|
640
|
-
|
|
656
|
+
if (node.left.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
657
|
+
return;
|
|
658
|
+
const { name } = node.left;
|
|
659
|
+
const assignmentCount = (assignmentCounts.get(name) ?? 0) + 1;
|
|
660
|
+
assignmentCounts.set(name, assignmentCount);
|
|
661
|
+
// Only a plain `=` makes the right-hand side the variable's value in
|
|
662
|
+
// general. A compound assignment leaves the prior value reachable
|
|
663
|
+
// (`key ||= K` is the old key OR K), so recording the operand would
|
|
664
|
+
// launder an unapproved key into an approved one.
|
|
665
|
+
if (node.operator === '=') {
|
|
666
|
+
variableAssignments.set(name, node.right);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
// The exception is a `||=`/`??=` onto a variable still holding
|
|
670
|
+
// `undefined`, which is both falsy and nullish: the assignment always
|
|
671
|
+
// happens, so the operand IS the value. A single declaration and a
|
|
672
|
+
// single assignment are what keep that provable — a second assignment
|
|
673
|
+
// anywhere puts a prior value back in play. `+=` is excluded because it
|
|
674
|
+
// concatenates onto `undefined` and yields neither operand.
|
|
675
|
+
//
|
|
676
|
+
// The operand is recorded rather than exempted, so it still has to be
|
|
677
|
+
// an approved constant to pass.
|
|
678
|
+
if ((node.operator === '||=' || node.operator === '??=') &&
|
|
679
|
+
declaredUndefined.has(name) &&
|
|
680
|
+
declarationCounts.get(name) === 1 &&
|
|
681
|
+
assignmentCount === 1) {
|
|
682
|
+
variableAssignments.set(name, node.right);
|
|
641
683
|
}
|
|
642
684
|
},
|
|
643
685
|
// Check useRouterState calls
|
|
@@ -5,6 +5,7 @@ const utils_1 = require("@typescript-eslint/utils");
|
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
6
|
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
7
7
|
const disableDirectives_1 = require("../utils/disableDirectives");
|
|
8
|
+
const hungarianNaming_1 = require("../utils/hungarianNaming");
|
|
8
9
|
const importInsertion_1 = require("../utils/importInsertion");
|
|
9
10
|
// React hooks to check
|
|
10
11
|
const HOOK_NAMES = new Set(['useEffect', 'useCallback', 'useMemo']);
|
|
@@ -62,10 +63,45 @@ function getLastPropertyName(expr) {
|
|
|
62
63
|
}
|
|
63
64
|
return null;
|
|
64
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* The name used when the base contributes nothing spellable. Free of any type
|
|
68
|
+
* marker by construction, and the concept the memo actually stands for — the
|
|
69
|
+
* hash of the dependency's CONTENT, which is what depending on `.length`
|
|
70
|
+
* failed to track.
|
|
71
|
+
*/
|
|
72
|
+
const BASE_FREE_HASH_NAME = 'contentHash';
|
|
73
|
+
function capitalize(name) {
|
|
74
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Names for the memo binding, in descending order of how much of the base they
|
|
78
|
+
* keep. `no-hungarian` ships as an error and is NOT fixable, so a name it
|
|
79
|
+
* rejects turns this fixer's work into a manual rename in a file that was clean
|
|
80
|
+
* beforehand (#1997) — every candidate is therefore checked against the same
|
|
81
|
+
* predicate that rule applies, and the first acceptable one wins.
|
|
82
|
+
*
|
|
83
|
+
* Which candidate that is follows from what `no-hungarian` rejects:
|
|
84
|
+
*
|
|
85
|
+
* - `<base>Hash` reads as the domain concept for almost every base and is the
|
|
86
|
+
* preferred spelling.
|
|
87
|
+
* - A single-letter `b`/`i` base makes `bHash`/`iHash` look like a Hungarian
|
|
88
|
+
* type prefix (b=boolean, i=integer) glued to a capital. `hashOf<Base>` says
|
|
89
|
+
* the same thing with the base out of the leading position.
|
|
90
|
+
* - A base that IS a type word or its abbreviation (`obj`, `arr`, `str`,
|
|
91
|
+
* `array`, `number`, ...) taints every name that carries it as a segment, so
|
|
92
|
+
* no base-preserving candidate can succeed. Dropping the type-coded base is
|
|
93
|
+
* exactly the rename `no-hungarian` asks for.
|
|
94
|
+
*/
|
|
95
|
+
function baseDerivedNames(base) {
|
|
96
|
+
return [`${base}Hash`, `hashOf${capitalize(base)}`];
|
|
97
|
+
}
|
|
65
98
|
function generateUniqueName(base, taken) {
|
|
66
|
-
const candidate =
|
|
99
|
+
const candidate = baseDerivedNames(base).find((name) => !(0, hungarianNaming_1.encodesTypeMarker)(name)) ??
|
|
100
|
+
BASE_FREE_HASH_NAME;
|
|
67
101
|
if (!taken.has(candidate))
|
|
68
102
|
return candidate;
|
|
103
|
+
// A numeric suffix disambiguates without reopening the naming question: it
|
|
104
|
+
// adds no word boundary, so it cannot turn an accepted name into a marker.
|
|
69
105
|
let i = 2;
|
|
70
106
|
while (taken.has(`${candidate}${i}`)) {
|
|
71
107
|
i++;
|
|
@@ -352,10 +352,6 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
|
|
|
352
352
|
checkFunctionReturnType(node);
|
|
353
353
|
}
|
|
354
354
|
},
|
|
355
|
-
// Check for array methods with type assertions
|
|
356
|
-
CallExpression() {
|
|
357
|
-
// Skip this check as we'll handle it in the TSAsExpression and TSTypeAssertion handlers
|
|
358
|
-
},
|
|
359
355
|
// Check for type assertions in expressions
|
|
360
356
|
TSAsExpression(node) {
|
|
361
357
|
// Allow 'as const' assertions if configured
|
|
@@ -122,6 +122,16 @@ exports.preferGlobalRouterStateKey = (0, createRule_1.createRule)({
|
|
|
122
122
|
const defaultImports = new Map();
|
|
123
123
|
// Track re-exports and variable assignments
|
|
124
124
|
const variableAssignments = new Map();
|
|
125
|
+
/**
|
|
126
|
+
* Names whose declaration leaves them holding `undefined` — `let key;` and
|
|
127
|
+
* `let key = undefined;` alike. Both falsy and nullish, which is what makes
|
|
128
|
+
* a later `||=`/`??=` onto one of them assign unconditionally.
|
|
129
|
+
*/
|
|
130
|
+
const declaredUndefined = new Set();
|
|
131
|
+
/** Declarations and assignments seen per name, to bound the above to the provable case. */
|
|
132
|
+
const declarationCounts = new Map();
|
|
133
|
+
const assignmentCounts = new Map();
|
|
134
|
+
const isUndefinedIdentifier = (node) => node.type === utils_1.AST_NODE_TYPES.Identifier && node.name === 'undefined';
|
|
125
135
|
const validQueryKeySources = new Set([
|
|
126
136
|
'util/routing/queryKeys',
|
|
127
137
|
'constants',
|
|
@@ -444,15 +454,46 @@ exports.preferGlobalRouterStateKey = (0, createRule_1.createRule)({
|
|
|
444
454
|
},
|
|
445
455
|
// Track variable declarations that might derive from query key constants
|
|
446
456
|
VariableDeclarator(node) {
|
|
447
|
-
if (node.id.type
|
|
448
|
-
|
|
457
|
+
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
458
|
+
return;
|
|
459
|
+
const { name } = node.id;
|
|
460
|
+
declarationCounts.set(name, (declarationCounts.get(name) ?? 0) + 1);
|
|
461
|
+
if (node.init) {
|
|
462
|
+
variableAssignments.set(name, node.init);
|
|
463
|
+
}
|
|
464
|
+
if (!node.init || isUndefinedIdentifier(node.init)) {
|
|
465
|
+
declaredUndefined.add(name);
|
|
449
466
|
}
|
|
450
467
|
},
|
|
451
468
|
// Track assignment expressions
|
|
452
469
|
AssignmentExpression(node) {
|
|
453
|
-
if (node.left.type
|
|
454
|
-
|
|
455
|
-
|
|
470
|
+
if (node.left.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
471
|
+
return;
|
|
472
|
+
const { name } = node.left;
|
|
473
|
+
const assignmentCount = (assignmentCounts.get(name) ?? 0) + 1;
|
|
474
|
+
assignmentCounts.set(name, assignmentCount);
|
|
475
|
+
// Only a plain `=` makes the right-hand side the variable's value in
|
|
476
|
+
// general. A compound assignment leaves the prior value reachable
|
|
477
|
+
// (`key ||= K` is the old key OR K), so recording the operand would
|
|
478
|
+
// launder an unapproved key into an approved one.
|
|
479
|
+
if (node.operator === '=') {
|
|
480
|
+
variableAssignments.set(name, node.right);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
// The exception is a `||=`/`??=` onto a variable still holding
|
|
484
|
+
// `undefined`, which is both falsy and nullish: the assignment always
|
|
485
|
+
// happens, so the operand IS the value. A single declaration and a
|
|
486
|
+
// single assignment are what keep that provable — a second assignment
|
|
487
|
+
// anywhere puts a prior value back in play. `+=` is excluded because it
|
|
488
|
+
// concatenates onto `undefined` and yields neither operand.
|
|
489
|
+
//
|
|
490
|
+
// The operand is recorded rather than exempted, so it still has to be
|
|
491
|
+
// an approved constant to pass.
|
|
492
|
+
if ((node.operator === '||=' || node.operator === '??=') &&
|
|
493
|
+
declaredUndefined.has(name) &&
|
|
494
|
+
declarationCounts.get(name) === 1 &&
|
|
495
|
+
assignmentCount === 1) {
|
|
496
|
+
variableAssignments.set(name, node.right);
|
|
456
497
|
}
|
|
457
498
|
},
|
|
458
499
|
// Check useRouterState calls
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirrors the type-marker predicate the shipped `no-hungarian` rule applies to
|
|
3
|
+
* a declaration name, so a fixer can refuse to SYNTHESISE a name that rule
|
|
4
|
+
* rejects. `no-hungarian` is not fixable, so a generated identifier it flags
|
|
5
|
+
* converts machine-resolvable work into a manual rename inside a file that was
|
|
6
|
+
* clean before `--fix` ran (#1997).
|
|
7
|
+
*
|
|
8
|
+
* Scope is the shape a fixer emits: an alphanumeric camelCase/PascalCase
|
|
9
|
+
* identifier. `no-hungarian`'s SCREAMING_SNAKE_CASE branch and its
|
|
10
|
+
* compound-noun / descriptive-suffix / <entity>Number / <domain>Symbol
|
|
11
|
+
* carve-outs are deliberately absent. Every one of them can only turn a
|
|
12
|
+
* rejection into an acceptance, and each is keyed on a trailing word a
|
|
13
|
+
* generated name does not carry, so their absence is unreachable for such a
|
|
14
|
+
* name and, if ever reached, costs the caller its next candidate rather than
|
|
15
|
+
* reintroducing the defect. `no-array-length-in-deps.test.ts` pins agreement
|
|
16
|
+
* with the real rule over the corpus of names its fixer can emit, so a change
|
|
17
|
+
* to either predicate surfaces there.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Whether `no-hungarian` reads `name` as encoding its own type.
|
|
21
|
+
*/
|
|
22
|
+
export declare function encodesTypeMarker(name: string): boolean;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Mirrors the type-marker predicate the shipped `no-hungarian` rule applies to
|
|
4
|
+
* a declaration name, so a fixer can refuse to SYNTHESISE a name that rule
|
|
5
|
+
* rejects. `no-hungarian` is not fixable, so a generated identifier it flags
|
|
6
|
+
* converts machine-resolvable work into a manual rename inside a file that was
|
|
7
|
+
* clean before `--fix` ran (#1997).
|
|
8
|
+
*
|
|
9
|
+
* Scope is the shape a fixer emits: an alphanumeric camelCase/PascalCase
|
|
10
|
+
* identifier. `no-hungarian`'s SCREAMING_SNAKE_CASE branch and its
|
|
11
|
+
* compound-noun / descriptive-suffix / <entity>Number / <domain>Symbol
|
|
12
|
+
* carve-outs are deliberately absent. Every one of them can only turn a
|
|
13
|
+
* rejection into an acceptance, and each is keyed on a trailing word a
|
|
14
|
+
* generated name does not carry, so their absence is unreachable for such a
|
|
15
|
+
* name and, if ever reached, costs the caller its next candidate rather than
|
|
16
|
+
* reintroducing the defect. `no-array-length-in-deps.test.ts` pins agreement
|
|
17
|
+
* with the real rule over the corpus of names its fixer can emit, so a change
|
|
18
|
+
* to either predicate surfaces there.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.encodesTypeMarker = void 0;
|
|
22
|
+
// Abbreviation type markers. No English word is spelled this way, so their
|
|
23
|
+
// presence as a whole camelCase segment is unambiguously a type tag.
|
|
24
|
+
const ABBREVIATION_MARKERS = ['str', 'num', 'int', 'bool', 'arr', 'obj'];
|
|
25
|
+
const COMMON_TYPES = [
|
|
26
|
+
'String',
|
|
27
|
+
'Number',
|
|
28
|
+
'Boolean',
|
|
29
|
+
'Array',
|
|
30
|
+
'Object',
|
|
31
|
+
'RegExp',
|
|
32
|
+
'Promise',
|
|
33
|
+
'Symbol',
|
|
34
|
+
'BigInt',
|
|
35
|
+
];
|
|
36
|
+
const TYPE_MARKERS = [
|
|
37
|
+
...ABBREVIATION_MARKERS,
|
|
38
|
+
'array',
|
|
39
|
+
...COMMON_TYPES,
|
|
40
|
+
'Class',
|
|
41
|
+
'Interface',
|
|
42
|
+
'Enum',
|
|
43
|
+
];
|
|
44
|
+
const ABBREVIATION_MARKER_SET = new Set(ABBREVIATION_MARKERS);
|
|
45
|
+
const FULL_TYPE_WORDS = new Set(COMMON_TYPES.map((word) => word.toLowerCase()));
|
|
46
|
+
// Single-letter Hungarian type prefixes (b=boolean, i=integer/index), matched
|
|
47
|
+
// only as a strict camelCase prefix. This is the arm that rejects `bHash`.
|
|
48
|
+
const SINGLE_LETTER_PREFIXES = new Set(['b', 'i']);
|
|
49
|
+
const CAMEL_SEGMENT_REGEX = /[A-Z]+(?![a-z])|[A-Z]?[a-z0-9]+|[A-Z]/g;
|
|
50
|
+
function splitCamelSegmentsRaw(name) {
|
|
51
|
+
return name.match(CAMEL_SEGMENT_REGEX) ?? [];
|
|
52
|
+
}
|
|
53
|
+
// A built-in type word carrying an internal capital (BigInt) splits into parts
|
|
54
|
+
// whose tail collides with an abbreviation marker (Int/int), so the parts are
|
|
55
|
+
// re-merged into one atomic segment before segment matching.
|
|
56
|
+
const MULTI_CAPITAL_TYPE_WORD_PARTS = COMMON_TYPES.map(splitCamelSegmentsRaw).filter((parts) => parts.length > 1);
|
|
57
|
+
function mergeMultiCapitalTypeWords(segments) {
|
|
58
|
+
const merged = [];
|
|
59
|
+
let index = 0;
|
|
60
|
+
while (index < segments.length) {
|
|
61
|
+
const match = MULTI_CAPITAL_TYPE_WORD_PARTS.find((parts) => parts.every((part, offset) => segments[index + offset]?.toLowerCase() === part.toLowerCase()));
|
|
62
|
+
if (match) {
|
|
63
|
+
merged.push(segments.slice(index, index + match.length).join(''));
|
|
64
|
+
index += match.length;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
merged.push(segments[index]);
|
|
68
|
+
index += 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return merged;
|
|
72
|
+
}
|
|
73
|
+
function splitCamelSegments(name) {
|
|
74
|
+
return mergeMultiCapitalTypeWords(splitCamelSegmentsRaw(name));
|
|
75
|
+
}
|
|
76
|
+
function isPascalCaseName(name) {
|
|
77
|
+
return /^[A-Z]/.test(name) && name !== name.toUpperCase();
|
|
78
|
+
}
|
|
79
|
+
// A PascalCase name whose type word qualifies a DIFFERENT head noun names a
|
|
80
|
+
// concept or relation (StringToNumber, NumberAmountEditor), not the value's own
|
|
81
|
+
// runtime type.
|
|
82
|
+
function isSemanticTypeConcept(name) {
|
|
83
|
+
const segments = splitCamelSegments(name);
|
|
84
|
+
if (segments.length < 2) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const hasTypeWord = segments.some((segment) => FULL_TYPE_WORDS.has(segment.toLowerCase()));
|
|
88
|
+
if (!hasTypeWord) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return segments.some((segment) => !FULL_TYPE_WORDS.has(segment.toLowerCase()));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Whether `no-hungarian` reads `name` as encoding its own type.
|
|
95
|
+
*/
|
|
96
|
+
function encodesTypeMarker(name) {
|
|
97
|
+
if (isPascalCaseName(name) && isSemanticTypeConcept(name)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (name.length > 1 &&
|
|
101
|
+
SINGLE_LETTER_PREFIXES.has(name[0]) &&
|
|
102
|
+
/[A-Z]/.test(name[1])) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
const normalizedName = name.toLowerCase();
|
|
106
|
+
return TYPE_MARKERS.some((marker) => {
|
|
107
|
+
const normalizedMarker = marker.toLowerCase();
|
|
108
|
+
if (normalizedName === normalizedMarker) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
// An abbreviation marker is a type tag only as a whole segment: "int"
|
|
112
|
+
// inside "Mint" or "point" is a word fragment, not a tag.
|
|
113
|
+
if (ABBREVIATION_MARKER_SET.has(normalizedMarker)) {
|
|
114
|
+
return splitCamelSegments(name).some((segment) => segment.toLowerCase() === normalizedMarker);
|
|
115
|
+
}
|
|
116
|
+
// A full type word tags the value's type only at a word boundary in
|
|
117
|
+
// leading or trailing position; a middle segment qualifies a domain concept.
|
|
118
|
+
if (normalizedName.startsWith(normalizedMarker) &&
|
|
119
|
+
normalizedName.length > normalizedMarker.length &&
|
|
120
|
+
/[A-Z0-9]/.test(name[normalizedMarker.length])) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return (normalizedName.endsWith(normalizedMarker) &&
|
|
124
|
+
normalizedName.length > normalizedMarker.length &&
|
|
125
|
+
(/[A-Z0-9]/.test(name[name.length - normalizedMarker.length - 1]) ||
|
|
126
|
+
/[A-Z]/.test(name[name.length - normalizedMarker.length])));
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
exports.encodesTypeMarker = encodesTypeMarker;
|
|
130
|
+
//# sourceMappingURL=hungarianNaming.js.map
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,57 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.150",
|
|
4
|
+
"date": "2026-08-13T19:12:01.840Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "enforce-exported-function-types",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
2000
|
|
11
|
+
],
|
|
12
|
+
"summary": "drop 28 inert empty-bodied selectors (closes #2000)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "enforce-querykey-ts",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
1999,
|
|
19
|
+
2001
|
|
20
|
+
],
|
|
21
|
+
"summary": "follow a `||=`/`??=` onto an undefined key (#2001); guard variable tracking on a plain `=` assignment (closes #1999)"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "no-type-assertion-returns",
|
|
25
|
+
"changeType": "fix",
|
|
26
|
+
"issues": [
|
|
27
|
+
2002
|
|
28
|
+
],
|
|
29
|
+
"summary": "drop the inert empty CallExpression handler (closes #2002)"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "prefer-global-router-state-key",
|
|
33
|
+
"changeType": "fix",
|
|
34
|
+
"issues": [
|
|
35
|
+
2001
|
|
36
|
+
],
|
|
37
|
+
"summary": "follow a `||=`/`??=` onto an undefined key (closes #2001)"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"version": "1.20.149",
|
|
43
|
+
"date": "2026-08-13T16:22:27.196Z",
|
|
44
|
+
"rules": [
|
|
45
|
+
{
|
|
46
|
+
"name": "no-array-length-in-deps",
|
|
47
|
+
"changeType": "fix",
|
|
48
|
+
"issues": [
|
|
49
|
+
1997
|
|
50
|
+
],
|
|
51
|
+
"summary": "emit a hash name no-hungarian accepts (closes #1997)"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
2
55
|
{
|
|
3
56
|
"version": "1.20.148",
|
|
4
57
|
"date": "2026-08-13T13:06:17.038Z",
|