@blumintinc/eslint-plugin-blumint 1.20.15 → 1.20.17

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.15',
226
+ version: '1.20.17',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -1,14 +1,54 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.noMockFirebaseAdmin = void 0;
7
+ const path_1 = __importDefault(require("path"));
4
8
  const utils_1 = require("@typescript-eslint/utils");
5
9
  const createRule_1 = require("../utils/createRule");
6
- const FIREBASE_ADMIN_PATHS = [
7
- 'firebaseAdmin',
8
- 'config/firebaseAdmin',
9
- 'src/config/firebaseAdmin',
10
- 'functions/src/config/firebaseAdmin',
11
- ];
10
+ const FIREBASE_ADMIN_MODULE = 'firebaseAdmin';
11
+ const MODULE_EXTENSION = /\.(?:tsx?|jsx?|mjs|cjs)$/;
12
+ /**
13
+ * Backend tier: `jest.setup.node.js` auto-mocks this module repo-wide, so a
14
+ * local `jest.mock()` bypasses the shared stub the rule exists to protect.
15
+ */
16
+ const BACKEND_FIREBASE_ADMIN = 'functions/src/config/firebaseAdmin';
17
+ /**
18
+ * Frontend tier: a distinct module with no shared mock (no `__mocks__` entry,
19
+ * no `jest.setup.node.js` registration), so nothing is bypassed by mocking it.
20
+ */
21
+ const FRONTEND_FIREBASE_ADMIN = 'src/config/firebaseAdmin';
22
+ const toPosix = (value) => value.replace(/\\/g, '/');
23
+ const targetsFirebaseAdmin = (mockPath) => {
24
+ const basename = toPosix(mockPath).split('/').pop() ?? '';
25
+ return basename.replace(MODULE_EXTENSION, '') === FIREBASE_ADMIN_MODULE;
26
+ };
27
+ const comparisonPathOf = (mockPath, filename) => {
28
+ // Bare and aliased specifiers (`src/config/firebaseAdmin`,
29
+ // `@project/functions/src/config/firebaseAdmin`) are resolved by the module
30
+ // resolver against roots/aliases rather than the importing file, so joining
31
+ // them onto the file's directory would fabricate a path that never exists.
32
+ if (!mockPath.startsWith('.')) {
33
+ return toPosix(mockPath);
34
+ }
35
+ return toPosix(path_1.default.resolve(path_1.default.dirname(filename), mockPath));
36
+ };
37
+ const bypassesSharedMock = (comparisonPath) => {
38
+ // Backend is tested first because the backend path *contains* the frontend
39
+ // path as a substring; checking frontend first would exempt every backend
40
+ // mock and silently disable the rule.
41
+ if (comparisonPath.includes(BACKEND_FIREBASE_ADMIN)) {
42
+ return true;
43
+ }
44
+ if (comparisonPath.includes(FRONTEND_FIREBASE_ADMIN)) {
45
+ return false;
46
+ }
47
+ // A specifier attributable to neither tier keeps the rule's protection:
48
+ // prefer reporting over silently allowing an unknown layout to shadow a
49
+ // shared mock.
50
+ return true;
51
+ };
12
52
  exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
13
53
  name: 'no-mock-firebase-admin',
14
54
  meta: {
@@ -43,26 +83,31 @@ exports.noMockFirebaseAdmin = (0, createRule_1.createRule)({
43
83
  node.arguments[0].type === utils_1.AST_NODE_TYPES.TemplateLiteral)) {
44
84
  let mockPath = '';
45
85
  if (node.arguments[0].type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
86
+ // An interpolated specifier names a module only known at runtime:
87
+ // `../../config/firebaseAdmin${env}` targets firebaseAdminStaging,
88
+ // not firebaseAdmin. Reading the leading quasi alone would attribute
89
+ // it to whichever module the static prefix happens to spell.
90
+ if (node.arguments[0].expressions.length > 0) {
91
+ return;
92
+ }
46
93
  mockPath = node.arguments[0].quasis[0].value.raw;
47
94
  }
48
95
  else if (node.arguments[0].type === utils_1.AST_NODE_TYPES.Literal) {
49
96
  mockPath = String(node.arguments[0].value);
50
97
  }
51
- const isFirebaseAdminMock = FIREBASE_ADMIN_PATHS.some((path) => mockPath.endsWith(path) &&
52
- !mockPath.endsWith('Helper') &&
53
- !mockPath.endsWith('utils') &&
54
- !mockPath.endsWith('test') &&
55
- !mockPath.endsWith('mock') &&
56
- !mockPath.endsWith('jest-mock'));
57
- if (isFirebaseAdminMock) {
58
- context.report({
59
- node,
60
- messageId: 'noMockFirebaseAdmin',
61
- data: {
62
- modulePath: mockPath,
63
- },
64
- });
98
+ if (!targetsFirebaseAdmin(mockPath)) {
99
+ return;
65
100
  }
101
+ if (!bypassesSharedMock(comparisonPathOf(mockPath, filename))) {
102
+ return;
103
+ }
104
+ context.report({
105
+ node,
106
+ messageId: 'noMockFirebaseAdmin',
107
+ data: {
108
+ modulePath: mockPath,
109
+ },
110
+ });
66
111
  }
67
112
  },
68
113
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.15",
3
+ "version": "1.20.17",
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.20.17",
4
+ "date": "2026-07-29T10:47:56.581Z",
5
+ "rules": [
6
+ {
7
+ "name": "no-mock-firebase-admin",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1386
11
+ ],
12
+ "summary": "skip interpolated jest.mock specifiers (closes #1386)"
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "version": "1.20.16",
18
+ "date": "2026-07-29T10:30:50.111Z",
19
+ "rules": [
20
+ {
21
+ "name": "no-mock-firebase-admin",
22
+ "changeType": "fix",
23
+ "issues": [
24
+ 1385
25
+ ],
26
+ "summary": "resolve specifier tier before flagging (closes #1385)"
27
+ }
28
+ ]
29
+ },
2
30
  {
3
31
  "version": "1.20.15",
4
32
  "date": "2026-07-29T08:50:35.880Z",