@blumintinc/eslint-plugin-blumint 1.20.24 → 1.20.26

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
@@ -223,7 +223,7 @@ function noFrontendImportsFromFunctionsPatterns(pattern) {
223
223
  module.exports = {
224
224
  meta: {
225
225
  name: '@blumintinc/eslint-plugin-blumint',
226
- version: '1.20.24',
226
+ version: '1.20.26',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -8,6 +8,7 @@ const path_1 = __importDefault(require("path"));
8
8
  const utils_1 = require("@typescript-eslint/utils");
9
9
  const createRule_1 = require("../utils/createRule");
10
10
  const ASTHelpers_1 = require("../utils/ASTHelpers");
11
+ const disableDirectives_1 = require("../utils/disableDirectives");
11
12
  const DEFAULT_IMPORT_PATH = 'functions/src/util/assertSafe';
12
13
  exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
13
14
  name: 'enforce-assert-safe-object-key',
@@ -35,6 +36,13 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
35
36
  create(context, [options]) {
36
37
  const importPath = options?.assertSafeImportPath || DEFAULT_IMPORT_PATH;
37
38
  let hasAssertSafeImport = false;
39
+ /**
40
+ * The `import { assertSafe }` statement rides on a single violation's fix,
41
+ * making that violation the file's import carrier. A suppressed carrier
42
+ * would take the import down with it while the surviving violations still
43
+ * emit `assertSafe(...)`, leaving the call unbound.
44
+ */
45
+ const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
38
46
  /**
39
47
  * Computes the module specifier for the injected assertSafe import.
40
48
  *
@@ -97,11 +105,20 @@ exports.enforceAssertSafeObjectKey = (0, createRule_1.createRule)({
97
105
  fixes.push(fixer.replaceText(node, `assertSafe(${argText})`));
98
106
  return fixes;
99
107
  };
108
+ // The report is emitted even when suppressed: ESLint discards it, and
109
+ // reporting keeps the user's disable directive "used" so that
110
+ // `--report-unused-disable-directives` does not flag it.
100
111
  const reportUseAssertSafe = (node, expressionText) => context.report({
101
112
  node,
102
113
  messageId: 'useAssertSafe',
103
114
  data: { key: expressionText },
104
115
  fix(fixer) {
116
+ // A suppressed report is dropped together with its fix. Producing no
117
+ // fix — and leaving the import unclaimed — passes the carrier slot to
118
+ // the first violation that survives.
119
+ if (isReportSuppressed(node)) {
120
+ return null;
121
+ }
105
122
  return createFixes(fixer, node, expressionText);
106
123
  },
107
124
  });
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enforceMemoizeAsync = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const disableDirectives_1 = require("../utils/disableDirectives");
6
7
  const MEMOIZE_MODULE = '@blumintinc/typescript-memoize';
7
8
  const ALLOWED_MEMOIZE_MODULES = new Set([MEMOIZE_MODULE, 'typescript-memoize']);
8
9
  /**
@@ -54,6 +55,13 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
54
55
  const memoizeAliases = new Map(); // alias -> source module
55
56
  const memoizeNamespaces = new Map(); // namespace -> source module
56
57
  let scheduledImportFix = false;
58
+ /**
59
+ * The `import { Memoize }` statement rides on a single violation's fix, so
60
+ * that violation is the file's import carrier. A suppressed carrier would
61
+ * take the import down with it while the surviving violations still emit
62
+ * `@Memoize()`, leaving a decorator with no import.
63
+ */
64
+ const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
57
65
  return {
58
66
  ImportDeclaration(node) {
59
67
  if (ALLOWED_MEMOIZE_MODULES.has(String(node.source.value))) {
@@ -119,10 +127,19 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
119
127
  if (hasDecorator) {
120
128
  return;
121
129
  }
130
+ // The report is emitted even when suppressed: ESLint discards it, and
131
+ // reporting keeps the user's disable directive "used" so that
132
+ // `--report-unused-disable-directives` does not flag it.
122
133
  context.report({
123
134
  node,
124
135
  messageId: 'requireMemoize',
125
136
  fix(fixer) {
137
+ // A suppressed report is dropped together with its fix. Producing
138
+ // no fix — and leaving the import unscheduled — passes the import
139
+ // to the first violation that survives.
140
+ if (isReportSuppressed(node)) {
141
+ return null;
142
+ }
126
143
  const fixes = [];
127
144
  const sourceCode = context.sourceCode;
128
145
  // Determine which identifier to use for the decorator
@@ -135,13 +152,13 @@ exports.enforceMemoizeAsync = (0, createRule_1.createRule)({
135
152
  }
136
153
  else if (memoizeAliases.size > 0) {
137
154
  // Find first alias from new package, fallback to any alias
138
- const newPackageAlias = Array.from(memoizeAliases.entries()).find(([_, pkg]) => pkg === MEMOIZE_MODULE)?.[0];
155
+ const newPackageAlias = Array.from(memoizeAliases.entries()).find(([, pkg]) => pkg === MEMOIZE_MODULE)?.[0];
139
156
  decoratorIdent =
140
157
  newPackageAlias || Array.from(memoizeAliases.keys())[0];
141
158
  }
142
159
  else if (memoizeNamespaces.size > 0) {
143
160
  // Prefer namespace from the new package if available
144
- const newPackageNs = Array.from(memoizeNamespaces.entries()).find(([_, pkg]) => pkg === MEMOIZE_MODULE)?.[0];
161
+ const newPackageNs = Array.from(memoizeNamespaces.entries()).find(([, pkg]) => pkg === MEMOIZE_MODULE)?.[0];
145
162
  const selectedNs = newPackageNs || Array.from(memoizeNamespaces.keys())[0];
146
163
  decoratorIdent = `${selectedNs}.Memoize`;
147
164
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.enforceMemoizeGetters = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const disableDirectives_1 = require("../utils/disableDirectives");
6
7
  const MEMOIZE_PREFERRED_MODULE = '@blumintinc/typescript-memoize';
7
8
  const MEMOIZE_MODULES = new Set([
8
9
  MEMOIZE_PREFERRED_MODULE,
@@ -58,6 +59,13 @@ exports.enforceMemoizeGetters = (0, createRule_1.createRule)({
58
59
  let memoizeNamespace = null;
59
60
  let hasNamedImport = false;
60
61
  let scheduledImportFix = false;
62
+ /**
63
+ * The `import { Memoize }` statement rides on a single violation's fix, so
64
+ * that violation is the file's import carrier. A suppressed carrier would
65
+ * take the import down with it while the surviving violations still emit
66
+ * `@Memoize()`, leaving a decorator with no import.
67
+ */
68
+ const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
61
69
  return {
62
70
  ImportDeclaration(node) {
63
71
  if (MEMOIZE_MODULES.has(String(node.source.value))) {
@@ -94,11 +102,20 @@ exports.enforceMemoizeGetters = (0, createRule_1.createRule)({
94
102
  const propertyName = node.computed
95
103
  ? '[computed]'
96
104
  : sourceCode.getText(node.key);
105
+ // The report is emitted even when suppressed: ESLint discards it, and
106
+ // reporting keeps the user's disable directive "used" so that
107
+ // `--report-unused-disable-directives` does not flag it.
97
108
  context.report({
98
109
  node,
99
110
  messageId: 'requireMemoizeGetter',
100
111
  data: { name: propertyName },
101
112
  fix(fixer) {
113
+ // A suppressed report is dropped together with its fix. Producing
114
+ // no fix — and leaving the import unscheduled — passes the import
115
+ // to the first violation that survives.
116
+ if (isReportSuppressed(node)) {
117
+ return null;
118
+ }
102
119
  const fixes = [];
103
120
  const getDecoratorIdent = () => {
104
121
  if (hasNamedImport) {
@@ -7,6 +7,7 @@ exports.enforceQueryKeyTs = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const utils_1 = require("@typescript-eslint/utils");
9
9
  const createRule_1 = require("../utils/createRule");
10
+ const disableDirectives_1 = require("../utils/disableDirectives");
10
11
  // The module's path below the project root doubles as the bare specifier,
11
12
  // which is precisely why the root tsconfig `paths` and the Jest mapper resolve
12
13
  // it.
@@ -97,6 +98,17 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
97
98
  * per-violation insertions that overlap and get dropped.
98
99
  */
99
100
  const pendingReports = [];
101
+ /**
102
+ * The queryKeys import rides on one violation's fix, which makes that
103
+ * violation the file's import carrier. ESLint collects fixes before it
104
+ * applies inline disable directives, so a suppressed carrier takes the
105
+ * import down with it while the surviving substitutions still land —
106
+ * leaving the file referencing constants nothing imports (#1410).
107
+ * Resolving suppression here keeps a suppressed violation out of the plan
108
+ * entirely: it neither claims the carrier slot nor contributes a specifier
109
+ * to the import, which would otherwise be imported and never used.
110
+ */
111
+ const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
100
112
  /**
101
113
  * `SourceCode#getScope` supersedes the deprecated `context.getScope`; the
102
114
  * fallback keeps the rule working on ESLint versions that predate it.
@@ -246,8 +258,11 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
246
258
  const resolutions = new Map();
247
259
  const missingConstants = [];
248
260
  const canImport = importSourceOf() !== null;
261
+ // The location handed to `context.report` below is what ESLint matches a
262
+ // directive against, so suppression is resolved from exactly that node.
263
+ const suppressed = new Set(pendingReports.filter((report) => isReportSuppressed(report.node)));
249
264
  for (const report of pendingReports) {
250
- if (!report.substitution) {
265
+ if (!report.substitution || suppressed.has(report)) {
251
266
  continue;
252
267
  }
253
268
  const { constant, scope } = report.substitution;
@@ -267,10 +282,16 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
267
282
  // The first applied substitution carries the import for every other one:
268
283
  // its fix range then starts at the top of the file and ends before the
269
284
  // remaining literals, so no two fixes of this rule overlap in a pass.
285
+ // Suppressed reports are skipped so the slot falls to a survivor.
270
286
  const importCarrier = pendingReports.find((report) => {
271
287
  const resolution = resolutions.get(report);
272
- return resolution !== undefined && resolution.state !== 'conflict';
288
+ return (!suppressed.has(report) &&
289
+ resolution !== undefined &&
290
+ resolution.state !== 'conflict');
273
291
  });
292
+ // Suppressed violations are still reported: ESLint discards them, and
293
+ // reporting keeps the user's directive "used" so that
294
+ // `--report-unused-disable-directives` does not flag it.
274
295
  for (const report of pendingReports) {
275
296
  context.report({
276
297
  node: report.node,
@@ -279,7 +300,8 @@ exports.enforceQueryKeyTs = (0, createRule_1.createRule)({
279
300
  fix(fixer) {
280
301
  const { substitution } = report;
281
302
  const resolution = resolutions.get(report);
282
- if (!substitution ||
303
+ if (suppressed.has(report) ||
304
+ !substitution ||
283
305
  !resolution ||
284
306
  resolution.state === 'conflict') {
285
307
  return null;
@@ -4,6 +4,7 @@ exports.enforceStableHashSpreadProps = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const ASTHelpers_1 = require("../utils/ASTHelpers");
6
6
  const createRule_1 = require("../utils/createRule");
7
+ const disableDirectives_1 = require("../utils/disableDirectives");
7
8
  const DEFAULT_HASH_IMPORT = {
8
9
  source: 'functions/src/util/hash/stableHash',
9
10
  importName: 'stableHash',
@@ -215,7 +216,12 @@ exports.enforceStableHashSpreadProps = (0, createRule_1.createRule)({
215
216
  ...(options.allowedHashFunctions ?? []),
216
217
  ]);
217
218
  const hookNames = new Set([...DEFAULT_HOOKS, ...userHookNames]);
219
+ // The `import { stableHash }` statement rides on a single violation's fix,
220
+ // so that violation is the file's import carrier. A suppressed carrier
221
+ // would take the import down with it while the surviving violations still
222
+ // emit `stableHash(...)`, leaving calls to an unbound identifier.
218
223
  let importPlanned = false;
224
+ const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
219
225
  const hashIdentifier = existingHashLocalNames[0] ?? hashImport.importName;
220
226
  const functionStack = [];
221
227
  function getCurrentComponentContext() {
@@ -294,11 +300,21 @@ exports.enforceStableHashSpreadProps = (0, createRule_1.createRule)({
294
300
  if (offendingElements.length === 0)
295
301
  return;
296
302
  const offendingNames = Array.from(new Set(offendingElements.map(({ name }) => name)));
303
+ // The report is emitted even when suppressed: ESLint discards it, and
304
+ // reporting keeps the user's disable directive "used" so that
305
+ // `--report-unused-disable-directives` does not flag it.
297
306
  context.report({
298
307
  node: depsArg,
299
308
  messageId: 'wrapSpreadPropsWithStableHash',
300
309
  data: { names: offendingNames.join(', ') },
301
310
  fix(fixer) {
311
+ // A suppressed report is dropped together with its fix. Producing
312
+ // no fix — and leaving the import unscheduled — passes the import
313
+ // to the first violation that survives. The check runs on the
314
+ // reported node so it resolves the same location ESLint does.
315
+ if (isReportSuppressed(depsArg)) {
316
+ return null;
317
+ }
302
318
  const fixes = [];
303
319
  const seen = new Set();
304
320
  for (const { node: targetNode } of offendingElements) {
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fastDeepEqualOverMicrodiff = void 0;
4
4
  const utils_1 = require("@typescript-eslint/utils");
5
5
  const createRule_1 = require("../utils/createRule");
6
+ const disableDirectives_1 = require("../utils/disableDirectives");
6
7
  exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
7
8
  name: 'fast-deep-equal-over-microdiff',
8
9
  meta: {
@@ -31,6 +32,13 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
31
32
  let fastDeepEqualImportName = 'isEqual';
32
33
  const reportedNodes = new Set();
33
34
  let plannedFastDeepEqualImport = false;
35
+ /**
36
+ * The `import ... from 'fast-deep-equal'` statement rides on a single
37
+ * violation's fix, so that violation is the file's import carrier. A
38
+ * suppressed carrier would take the import down with it while the surviving
39
+ * violations still emit `isEqual(...)` calls, leaving them unbound.
40
+ */
41
+ const isReportSuppressed = (0, disableDirectives_1.createSuppressionChecker)(context);
34
42
  const isChainExpression = (node) => node.type === utils_1.AST_NODE_TYPES.ChainExpression;
35
43
  function isMicrodiffCallee(callee) {
36
44
  if (callee.type === utils_1.AST_NODE_TYPES.Identifier &&
@@ -292,6 +300,12 @@ exports.fastDeepEqualOverMicrodiff = (0, createRule_1.createRule)({
292
300
  * Create a fix for replacing microdiff equality check with fast-deep-equal
293
301
  */
294
302
  function createFix(fixer, node, diffCall, isEquality) {
303
+ // A suppressed report is dropped together with its fix. Producing no fix
304
+ // — and leaving the import unscheduled — passes the carrier slot to the
305
+ // first violation that survives.
306
+ if (isReportSuppressed(node)) {
307
+ return null;
308
+ }
295
309
  const args = diffCall.arguments;
296
310
  if (args.length !== 2) {
297
311
  return null; // Can't fix if not exactly 2 arguments