@blumintinc/eslint-plugin-blumint 1.19.14 → 1.19.16

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
@@ -222,7 +222,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
222
222
  module.exports = {
223
223
  meta: {
224
224
  name: '@blumintinc/eslint-plugin-blumint',
225
- version: '1.19.14',
225
+ version: '1.19.16',
226
226
  },
227
227
  parseOptions: {
228
228
  ecmaVersion: 2020,
@@ -256,10 +256,48 @@ const BUILT_IN_METHODS = new Set([
256
256
  'catch',
257
257
  'finally',
258
258
  ]);
259
+ // The camelCase/PascalCase splitter's core pattern. Kept separate from
260
+ // splitCamelSegments so precomputed constants (below) can fragment known type
261
+ // words without recursing through the merge step.
262
+ const CAMEL_SEGMENT_REGEX = /[A-Z]+(?![a-z])|[A-Z]?[a-z0-9]+|[A-Z]/g;
263
+ function splitCamelSegmentsRaw(name) {
264
+ return name.match(CAMEL_SEGMENT_REGEX) ?? [];
265
+ }
266
+ // COMMON_TYPES words containing an internal capital — only `BigInt` today — are
267
+ // fragmented by the splitter into parts (["Big","Int"]) where a fragment ("Int")
268
+ // collides with an abbreviation marker ("int"), so ANY identifier containing
269
+ // BigInt would spuriously match that marker regardless of position (#1317).
270
+ // Precompute those fragment sequences so splitCamelSegments can re-merge them
271
+ // into a single atomic segment, ensuring a built-in type word is never mistaken
272
+ // for a Hungarian abbreviation tag.
273
+ const MULTI_CAPITAL_TYPE_WORD_PARTS = COMMON_TYPES.map(splitCamelSegmentsRaw).filter((parts) => parts.length > 1);
274
+ // Re-merge any consecutive segments that reconstitute a multi-capital built-in
275
+ // type word (Big + Int -> BigInt). Case-insensitive so lower/upper camelCase
276
+ // variants collapse identically.
277
+ function mergeMultiCapitalTypeWords(segments) {
278
+ if (MULTI_CAPITAL_TYPE_WORD_PARTS.length === 0) {
279
+ return segments;
280
+ }
281
+ const merged = [];
282
+ let index = 0;
283
+ while (index < segments.length) {
284
+ const match = MULTI_CAPITAL_TYPE_WORD_PARTS.find((parts) => parts.every((part, offset) => segments[index + offset]?.toLowerCase() === part.toLowerCase()));
285
+ if (match) {
286
+ merged.push(segments.slice(index, index + match.length).join(''));
287
+ index += match.length;
288
+ }
289
+ else {
290
+ merged.push(segments[index]);
291
+ index += 1;
292
+ }
293
+ }
294
+ return merged;
295
+ }
259
296
  // Split a PascalCase/camelCase identifier into its word segments
260
297
  // (e.g. "StringToNumber" -> ["String","To","Number"], "FuncKeys" -> ["Func","Keys"]).
298
+ // Multi-capital built-in type words (BigInt) are kept as one atomic segment.
261
299
  function splitCamelSegments(name) {
262
- return name.match(/[A-Z]+(?![a-z])|[A-Z]?[a-z0-9]+|[A-Z]/g) ?? [];
300
+ return mergeMultiCapitalTypeWords(splitCamelSegmentsRaw(name));
263
301
  }
264
302
  // A TYPE name (alias/interface/class) is exempt from a full-type-word marker when
265
303
  // that marker is one clean PascalCase segment among OTHER descriptive segments —
@@ -280,6 +318,13 @@ function isSemanticTypeConcept(typeName) {
280
318
  // concept (e.g. Extract+Number) rather than bare type tags glued together.
281
319
  return segments.some((segment) => !FULL_TYPE_WORDS.has(segment.toLowerCase()));
282
320
  }
321
+ // A PascalCase declaration name (leading capital, at least one lowercase letter):
322
+ // a component, class, or type identifier. Distinguished from SCREAMING_SNAKE_CASE
323
+ // constants (all caps) and lowercase-initial variables, which are handled on their
324
+ // own code paths.
325
+ function isPascalCaseName(name) {
326
+ return /^[A-Z]/.test(name) && name !== name.toUpperCase();
327
+ }
283
328
  // Is `name` a domain compound of the form <entity>Number, where the word directly
284
329
  // before the trailing "Number" is a known domain-entity noun (issueNumber,
285
330
  // lineNumber, roundNumber, versionNumber)? Only the LAST head segment is
@@ -336,7 +381,17 @@ exports.noHungarian = (0, createRule_1.createRule)({
336
381
  // Type names whose type-word denotes a concept/relation (StringToNumber,
337
382
  // CapitalizedString, FuncKeys, PromiseOrValue) are not Hungarian — the word
338
383
  // is part of the type's meaning, like the allowed compound noun PhoneNumber.
339
- if (isTypeName && isSemanticTypeConcept(variableName)) {
384
+ //
385
+ // Extended to any PascalCase declaration (components, classes, functions):
386
+ // when a built-in type word qualifies a DIFFERENT head noun the value is a
387
+ // component / props type, never a number/bigint (NumberAmountEditor,
388
+ // BigIntAmountEditorProps) — the leading-position analog of the
389
+ // DOMAIN_NUMBER_HEAD_NOUNS suffix carve-out, mirroring Intl.NumberFormat /
390
+ // StringBuilder / NumberFormatter. Keyed on PascalCase because a
391
+ // lowercase-initial variable (numberCount, stringValue) DOES encode its own
392
+ // value's type and must keep firing (#1317).
393
+ if ((isTypeName || isPascalCaseName(variableName)) &&
394
+ isSemanticTypeConcept(variableName)) {
340
395
  return false;
341
396
  }
342
397
  // Single-letter Hungarian prefixes (bIsActive, iCount): a lone b/i directly
@@ -661,6 +661,15 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
661
661
  ? (suggestedNameCounts.get(classBody)?.get(scopeKey) ?? 0) > 1
662
662
  : false;
663
663
  const isAsync = node.value.async;
664
+ // Only `private` methods are safe to autofix. A public / protected /
665
+ // unspecified-accessibility method is API surface whose call sites of
666
+ // the form `instance.method()` may live in other files this
667
+ // single-file rule never sees. Converting such a method to a getter
668
+ // silently breaks every external caller (the call would invoke the
669
+ // getter's return value), so the fix must be withheld for anything
670
+ // not demonstrably private. The report (nudge) is still emitted.
671
+ const isPrivate = node.accessibility === 'private' ||
672
+ node.key.type === utils_1.AST_NODE_TYPES.PrivateIdentifier;
664
673
  context.report({
665
674
  node: node.key,
666
675
  messageId: sideEffectReason
@@ -671,7 +680,8 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
671
680
  suggestedName,
672
681
  reason: sideEffectReason ?? 'it returns a value',
673
682
  },
674
- fix: sideEffectReason ||
683
+ fix: !isPrivate ||
684
+ sideEffectReason ||
675
685
  isAsync ||
676
686
  !leftParen ||
677
687
  !rightParen ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.19.14",
3
+ "version": "1.19.16",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,32 @@
1
1
  [
2
+ {
3
+ "version": "1.19.16",
4
+ "date": "2026-07-18T06:29:03.568Z",
5
+ "rules": [
6
+ {
7
+ "name": "prefer-getter-over-parameterless-method",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1318
11
+ ],
12
+ "summary": "withhold autofix for non-private methods (closes #1318)"
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "version": "1.19.15",
18
+ "date": "2026-07-17T23:29:51.136Z",
19
+ "rules": [
20
+ {
21
+ "name": "no-hungarian",
22
+ "changeType": "fix",
23
+ "issues": [
24
+ 1317
25
+ ],
26
+ "summary": "exempt PascalCase domain-qualifier names using built-in type words (closes #1317)"
27
+ }
28
+ ]
29
+ },
2
30
  {
3
31
  "version": "1.19.14",
4
32
  "date": "2026-07-17T21:35:22.880Z",