@blumintinc/eslint-plugin-blumint 1.20.77 → 1.20.79

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.77',
226
+ version: '1.20.79',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -14,12 +14,17 @@ exports.enforceFirestorePathUtils = (0, createRule_1.createRule)({
14
14
  },
15
15
  schema: [],
16
16
  messages: {
17
- requirePathUtil: 'Use a utility function for Firestore paths to ensure type safety and maintainability. Instead of `doc("users/" + userId)`, create and use a utility function: `const toUserPath = (id: string) => `users/${id}`; doc(toUserPath(userId))`.',
17
+ requirePathUtil: 'Use a utility function for Firestore paths to ensure type safety and maintainability. Instead of `db.doc("users/" + userId)`, create and use a utility function: `const toUserPath = (id: string) => `users/${id}`; db.doc(toUserPath(userId))`.',
18
18
  },
19
19
  },
20
20
  defaultOptions: [],
21
21
  create(context) {
22
22
  function isFirestoreCall(node) {
23
+ // Requiring an explicit receiver keeps unrelated `doc(...)`/`collection(...)`
24
+ // calls out of scope, at the cost of not covering the modular SDK, whose
25
+ // path sits in the second argument. The requirePathUtil example is written
26
+ // with a receiver for the same reason: a bare call is never reportable, so
27
+ // an example without one points at code this rule does not govern.
23
28
  if (node.callee.type !== utils_1.AST_NODE_TYPES.MemberExpression) {
24
29
  return false;
25
30
  }
@@ -34,6 +39,24 @@ exports.enforceFirestorePathUtils = (0, createRule_1.createRule)({
34
39
  typeof node.value === 'string') ||
35
40
  node.type === utils_1.AST_NODE_TYPES.TemplateLiteral);
36
41
  }
42
+ /**
43
+ * A `+` chain qualifies as an inline path only when a string literal or
44
+ * template literal appears somewhere in it: that literal is the hard-coded
45
+ * path fragment the rule exists to push behind a helper. The walk recurses
46
+ * because `'teams/' + teamId + '/members'` parses as a left-nested
47
+ * BinaryExpression, which leaves the literals below the outermost operands.
48
+ * A chain of opaque operands (`a + b`) constructs no path fragment inline,
49
+ * so it keeps the same indirection allowance as a bare variable.
50
+ */
51
+ function isInlinePathExpression(node) {
52
+ if (isStringLiteralOrTemplate(node)) {
53
+ return true;
54
+ }
55
+ return (node.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
56
+ node.operator === '+' &&
57
+ (isInlinePathExpression(node.left) ||
58
+ isInlinePathExpression(node.right)));
59
+ }
37
60
  function isUtilityFunction(node) {
38
61
  if (node.type !== utils_1.AST_NODE_TYPES.CallExpression) {
39
62
  return false;
@@ -59,8 +82,9 @@ exports.enforceFirestorePathUtils = (0, createRule_1.createRule)({
59
82
  if (isUtilityFunction(pathArg)) {
60
83
  return;
61
84
  }
62
- // Skip if it's a variable or other non-literal expression
63
- if (!isStringLiteralOrTemplate(pathArg)) {
85
+ // Skip if it's a variable or other expression that hides construction
86
+ // behind a name rather than assembling the path inline
87
+ if (!isInlinePathExpression(pathArg)) {
64
88
  return;
65
89
  }
66
90
  // Skip test files
@@ -14,7 +14,7 @@ exports.enforceRealtimedbPathUtils = (0, createRule_1.createRule)({
14
14
  },
15
15
  schema: [],
16
16
  messages: {
17
- requirePathUtil: 'Use a utility function for Realtime Database paths to ensure type safety and maintainability. Instead of `ref("users/" + userId)`, create and use a utility function: `const toUserPath = (id: string) => `users/${id}`; ref(toUserPath(userId))`.',
17
+ requirePathUtil: 'Use a utility function for Realtime Database paths to ensure type safety and maintainability. Instead of `admin.database().ref(`users/${userId}`)`, create and use a utility function: `const toUserPath = (id: string) => `users/${id}`; admin.database().ref(toUserPath(userId))`.',
18
18
  },
19
19
  },
20
20
  defaultOptions: [],
@@ -59,6 +59,24 @@ exports.enforceRealtimedbPathUtils = (0, createRule_1.createRule)({
59
59
  typeof node.value === 'string') ||
60
60
  node.type === utils_1.AST_NODE_TYPES.TemplateLiteral);
61
61
  }
62
+ /**
63
+ * A `+` chain qualifies as an inline path only when a string literal or
64
+ * template literal appears somewhere in it: that literal is the hard-coded
65
+ * path fragment the rule exists to push behind a helper. The walk recurses
66
+ * because `'users/' + userId + '/posts'` parses as a left-nested
67
+ * BinaryExpression, which leaves the literals below the outermost operands.
68
+ * A chain of opaque operands (`a + b`) constructs no path fragment inline,
69
+ * so it keeps the same indirection allowance as a bare variable.
70
+ */
71
+ function isInlinePathExpression(node) {
72
+ if (isStringLiteralOrTemplate(node)) {
73
+ return true;
74
+ }
75
+ return (node.type === utils_1.AST_NODE_TYPES.BinaryExpression &&
76
+ node.operator === '+' &&
77
+ (isInlinePathExpression(node.left) ||
78
+ isInlinePathExpression(node.right)));
79
+ }
62
80
  function isUtilityFunction(node) {
63
81
  if (node.type !== utils_1.AST_NODE_TYPES.CallExpression) {
64
82
  return false;
@@ -84,8 +102,9 @@ exports.enforceRealtimedbPathUtils = (0, createRule_1.createRule)({
84
102
  if (isUtilityFunction(pathArg)) {
85
103
  return;
86
104
  }
87
- // Skip if it's a variable or other non-literal expression
88
- if (!isStringLiteralOrTemplate(pathArg)) {
105
+ // Skip if it's a variable or other expression that hides construction
106
+ // behind a name rather than assembling the path inline
107
+ if (!isInlinePathExpression(pathArg)) {
89
108
  return;
90
109
  }
91
110
  // Skip test files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.77",
3
+ "version": "1.20.79",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,40 @@
1
1
  [
2
+ {
3
+ "version": "1.20.79",
4
+ "date": "2026-08-02T08:54:03.464Z",
5
+ "rules": [
6
+ {
7
+ "name": "enforce-firestore-path-utils",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 1613
11
+ ],
12
+ "summary": "cite a reportable example in the message (closes #1613)"
13
+ }
14
+ ]
15
+ },
16
+ {
17
+ "version": "1.20.78",
18
+ "date": "2026-08-02T08:45:25.049Z",
19
+ "rules": [
20
+ {
21
+ "name": "enforce-firestore-path-utils",
22
+ "changeType": "fix",
23
+ "issues": [
24
+ 1611
25
+ ],
26
+ "summary": "report concatenated inline paths (closes #1611)"
27
+ },
28
+ {
29
+ "name": "enforce-realtimedb-path-utils",
30
+ "changeType": "fix",
31
+ "issues": [
32
+ 1612
33
+ ],
34
+ "summary": "report concatenated inline paths (closes #1612)"
35
+ }
36
+ ]
37
+ },
2
38
  {
3
39
  "version": "1.20.77",
4
40
  "date": "2026-08-02T08:02:01.009Z",