@blumintinc/eslint-plugin-blumint 1.19.4 → 1.19.6

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.4',
225
+ version: '1.19.6',
226
226
  },
227
227
  parseOptions: {
228
228
  ecmaVersion: 2020,
@@ -77,8 +77,45 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
77
77
  }
78
78
  return [];
79
79
  }
80
+ function isExportedClass(node) {
81
+ const parent = node.parent;
82
+ if (!parent) {
83
+ return false;
84
+ }
85
+ if (parent.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
86
+ parent.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration) {
87
+ return true;
88
+ }
89
+ // `export const Foo = class { ... }` exposes the class expression to other
90
+ // files just as an exported declaration does.
91
+ if (node.type === utils_1.AST_NODE_TYPES.ClassExpression &&
92
+ parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
93
+ const declaration = parent.parent;
94
+ const exportNode = declaration?.parent;
95
+ return (exportNode?.type === utils_1.AST_NODE_TYPES.ExportNamedDeclaration ||
96
+ exportNode?.type === utils_1.AST_NODE_TYPES.ExportDefaultDeclaration);
97
+ }
98
+ return false;
99
+ }
80
100
  function collectClassInfo(node) {
81
101
  const methods = new Map();
102
+ // A method's `this.method(...)` call sites are confined to this file only
103
+ // when the class cannot be extended from another file. An `abstract` class
104
+ // exists to be subclassed, and an exported class can be subclassed
105
+ // anywhere; either way subclasses may call an inherited non-private method
106
+ // with a different `this.<member>`, so those methods are externally
107
+ // reachable and must not be reported. See issue #1309.
108
+ const classIsExtensible = Boolean(node.abstract) ||
109
+ isExportedClass(node);
110
+ function computeExternallyReachable(methodName, accessibility) {
111
+ if (!classIsExtensible) {
112
+ return false;
113
+ }
114
+ // `private` (TS) and `#name` (ECMAScript) methods are never inherited,
115
+ // so all of their call sites live in the declaring class body.
116
+ const isPrivate = accessibility === 'private' || methodName.startsWith('#');
117
+ return !isPrivate;
118
+ }
82
119
  function setMethod(methodName, meta) {
83
120
  /**
84
121
  * Prefer instance methods over static methods when names collide.
@@ -102,7 +139,14 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
102
139
  for (const member of node.body.body) {
103
140
  if (member.type === utils_1.AST_NODE_TYPES.MethodDefinition ||
104
141
  member.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition) {
105
- if (member.kind === 'constructor') {
142
+ // Only genuine methods (`kind: 'method'`) are invoked directly as
143
+ // `this.x(args)`. A `get`/`set` accessor is also a MethodDefinition,
144
+ // but `this.accessor(args)` evaluates the accessor with zero arguments
145
+ // and then calls its RETURN VALUE — so when that value is an
146
+ // externally-supplied function it legitimately needs instance state
147
+ // threaded in, and flagging it is a false positive (issue #1308). The
148
+ // constructor is likewise never called as `this.constructor(...)`.
149
+ if (member.kind !== 'method') {
106
150
  continue;
107
151
  }
108
152
  const methodName = getNameFromPropertyName(member.key);
@@ -113,6 +157,7 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
113
157
  params: getMethodParams(member),
114
158
  isStatic: Boolean(member.static),
115
159
  isAbstract: member.type === utils_1.AST_NODE_TYPES.TSAbstractMethodDefinition,
160
+ externallyReachable: computeExternallyReachable(methodName, member.accessibility),
116
161
  });
117
162
  }
118
163
  else if (member.type === utils_1.AST_NODE_TYPES.PropertyDefinition) {
@@ -130,6 +175,7 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
130
175
  params,
131
176
  isStatic: false,
132
177
  isAbstract: false,
178
+ externallyReachable: computeExternallyReachable(methodName, member.accessibility),
133
179
  });
134
180
  }
135
181
  }
@@ -425,6 +471,12 @@ exports.noRedundantThisParams = (0, createRule_1.createRule)({
425
471
  for (const [, propStats] of argProperties) {
426
472
  if (propStats.callsWithProperty === methodStats.totalCalls) {
427
473
  for (const violation of propStats.violations) {
474
+ // Skip methods whose call sites are not provably confined to
475
+ // this file: a subclass elsewhere may thread a different
476
+ // `this.<member>` through the same parameter (issue #1309).
477
+ if (violation.methodMeta.externallyReachable) {
478
+ continue;
479
+ }
428
480
  reportAccess(methodName, violation.methodMeta, argIndex, violation.access);
429
481
  }
430
482
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.19.4",
3
+ "version": "1.19.6",
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.6",
4
+ "date": "2026-07-16T01:27:07.130Z",
5
+ "rules": [
6
+ {
7
+ "name": "no-redundant-this-params",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1309
11
+ ],
12
+ "summary": "skip externally-reachable methods (closes #1309)"
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "version": "1.19.5",
18
+ "date": "2026-07-15T15:24:16.132Z",
19
+ "rules": [
20
+ {
21
+ "name": "no-redundant-this-params",
22
+ "changeType": "fix",
23
+ "issues": [
24
+ 1308
25
+ ],
26
+ "summary": "skip get/set accessors when resolving callees (closes #1308)"
27
+ }
28
+ ]
29
+ },
2
30
  {
3
31
  "version": "1.19.4",
4
32
  "date": "2026-07-14T18:24:36.895Z",