@blumintinc/eslint-plugin-blumint 1.16.2 → 1.17.1

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.
Files changed (51) hide show
  1. package/README.md +21 -0
  2. package/lib/index.js +64 -1
  3. package/lib/rules/enforce-boolean-naming-prefixes.js +30 -14
  4. package/lib/rules/enforce-cloud-function-id-length.d.ts +5 -0
  5. package/lib/rules/enforce-cloud-function-id-length.js +104 -0
  6. package/lib/rules/enforce-is-prefix-validators.d.ts +13 -0
  7. package/lib/rules/enforce-is-prefix-validators.js +304 -0
  8. package/lib/rules/enforce-m3-sentence-case.d.ts +12 -0
  9. package/lib/rules/enforce-m3-sentence-case.js +430 -0
  10. package/lib/rules/enforce-snapshot-state-narrowing.d.ts +10 -0
  11. package/lib/rules/enforce-snapshot-state-narrowing.js +281 -0
  12. package/lib/rules/enforce-types-directory-placement.d.ts +9 -0
  13. package/lib/rules/enforce-types-directory-placement.js +276 -0
  14. package/lib/rules/no-direct-function-state.d.ts +8 -0
  15. package/lib/rules/no-direct-function-state.js +285 -0
  16. package/lib/rules/no-fill-template-mutation.d.ts +1 -0
  17. package/lib/rules/no-fill-template-mutation.js +324 -0
  18. package/lib/rules/no-hungarian.js +18 -26
  19. package/lib/rules/no-portal-inside-tooltip.d.ts +10 -0
  20. package/lib/rules/no-portal-inside-tooltip.js +219 -0
  21. package/lib/rules/no-redundant-boolean-callback-props.d.ts +11 -0
  22. package/lib/rules/no-redundant-boolean-callback-props.js +355 -0
  23. package/lib/rules/no-satisfies-in-frontend-bundle.d.ts +8 -0
  24. package/lib/rules/no-satisfies-in-frontend-bundle.js +126 -0
  25. package/lib/rules/no-single-dismiss-dialog-button.d.ts +7 -0
  26. package/lib/rules/no-single-dismiss-dialog-button.js +127 -0
  27. package/lib/rules/no-stablehash-react-nodes.d.ts +1 -0
  28. package/lib/rules/no-stablehash-react-nodes.js +325 -0
  29. package/lib/rules/parallelize-loop-awaits.d.ts +8 -0
  30. package/lib/rules/parallelize-loop-awaits.js +582 -0
  31. package/lib/rules/prefer-flat-transform-each-keys.d.ts +1 -0
  32. package/lib/rules/prefer-flat-transform-each-keys.js +228 -0
  33. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +1 -0
  34. package/lib/rules/prefer-getter-over-parameterless-method.js +44 -0
  35. package/lib/rules/prefer-spread-over-reassembly.d.ts +5 -0
  36. package/lib/rules/prefer-spread-over-reassembly.js +401 -0
  37. package/lib/rules/prefer-sx-prop-over-system-props.d.ts +9 -0
  38. package/lib/rules/prefer-sx-prop-over-system-props.js +401 -0
  39. package/lib/rules/prefer-use-base62-id.d.ts +8 -0
  40. package/lib/rules/prefer-use-base62-id.js +483 -0
  41. package/lib/rules/prefer-use-theme.d.ts +1 -0
  42. package/lib/rules/prefer-use-theme.js +206 -0
  43. package/lib/rules/prefer-utility-function-own-file.d.ts +9 -0
  44. package/lib/rules/prefer-utility-function-own-file.js +505 -0
  45. package/lib/rules/react-memoize-literals.js +106 -1
  46. package/lib/rules/require-props-composition.d.ts +10 -0
  47. package/lib/rules/require-props-composition.js +433 -0
  48. package/lib/rules/require-server-timestamp-for-firestore-dates.d.ts +9 -0
  49. package/lib/rules/require-server-timestamp-for-firestore-dates.js +313 -0
  50. package/package.json +1 -1
  51. package/release-manifest.json +212 -0
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preferFlatTransformEachKeys = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ // Propagation strategy shape signals: an object literal with one of these
7
+ // properties (besides transformEach) is treated as a propagation strategy.
8
+ const STRATEGY_SHAPE_KEYS = new Set([
9
+ 'resolveAll',
10
+ 'queryResolveAll',
11
+ 'numericFieldPathConfig',
12
+ 'upsert',
13
+ 'sourceDeletionOverride',
14
+ ]);
15
+ function isObjectExpression(node) {
16
+ return !!node && node.type === utils_1.AST_NODE_TYPES.ObjectExpression;
17
+ }
18
+ function isProperty(node) {
19
+ return node.type === utils_1.AST_NODE_TYPES.Property;
20
+ }
21
+ // Return the string name of a non-computed property key, or null.
22
+ function getStaticKeyName(prop) {
23
+ if (prop.computed)
24
+ return null;
25
+ if (prop.key.type === utils_1.AST_NODE_TYPES.Identifier)
26
+ return prop.key.name;
27
+ if (prop.key.type === utils_1.AST_NODE_TYPES.Literal &&
28
+ typeof prop.key.value === 'string')
29
+ return prop.key.value;
30
+ return null;
31
+ }
32
+ // A key is a "dot-notation key" if it contains a dot (e.g., 'foo.bar').
33
+ // Such keys represent leaf paths — their values can be any shape.
34
+ function isDotNotationKey(keyName) {
35
+ return keyName.includes('.');
36
+ }
37
+ // Determine whether an object-expression property key is a computed key.
38
+ // Computed keys (bracket notation, including template literals) represent
39
+ // dynamic leaf paths and are therefore exempt from the flat-key requirement.
40
+ // Template literal keys always have `computed: true` in the AST, so a single
41
+ // check on `prop.computed` is sufficient.
42
+ function isComputedKey(prop) {
43
+ return prop.computed;
44
+ }
45
+ // Find a direct Property child by name inside an ObjectExpression.
46
+ function findProp(obj, name) {
47
+ for (const p of obj.properties) {
48
+ if (!isProperty(p))
49
+ continue;
50
+ const k = getStaticKeyName(p);
51
+ if (k === name)
52
+ return p;
53
+ }
54
+ return null;
55
+ }
56
+ // Determine whether an ObjectExpression looks like a propagation strategy:
57
+ // it has a `transformEach` property AND at least one of the known strategy
58
+ // shape keys.
59
+ function isStrategyObject(obj) {
60
+ let hasTransformEach = false;
61
+ let hasStrategyKey = false;
62
+ for (const p of obj.properties) {
63
+ if (!isProperty(p))
64
+ continue;
65
+ const k = getStaticKeyName(p);
66
+ if (!k)
67
+ continue;
68
+ if (k === 'transformEach')
69
+ hasTransformEach = true;
70
+ if (STRATEGY_SHAPE_KEYS.has(k))
71
+ hasStrategyKey = true;
72
+ }
73
+ return hasTransformEach && hasStrategyKey;
74
+ }
75
+ // Return true if the strategy uses resolveSelf (deleted-source short-circuit,
76
+ // so nested objects are safe). Checks: resolveAll's value is an Identifier
77
+ // named 'resolveSelf'.
78
+ function usesResolveSelf(obj) {
79
+ const resolveAllProp = findProp(obj, 'resolveAll');
80
+ if (!resolveAllProp)
81
+ return false;
82
+ const val = resolveAllProp.value;
83
+ return val.type === utils_1.AST_NODE_TYPES.Identifier && val.name === 'resolveSelf';
84
+ }
85
+ // Extract the effective "data" object from a return value:
86
+ // - If the returned object has an `afterData` property, return its value
87
+ // (since the outer object is a structural wrapper, not a propagation map).
88
+ // - Otherwise return the object itself.
89
+ function getDataObject(obj) {
90
+ const afterDataProp = findProp(obj, 'afterData');
91
+ if (afterDataProp) {
92
+ const val = afterDataProp.value;
93
+ if (isObjectExpression(val))
94
+ return val;
95
+ // afterData exists but its value isn't a literal (e.g. a variable) — skip.
96
+ return null;
97
+ }
98
+ return obj;
99
+ }
100
+ // Check whether a variable-binding return follows the pattern:
101
+ // const result = { ... };
102
+ // return result;
103
+ // Returns the initialiser ObjectExpression if found, otherwise null.
104
+ function resolveVariableBinding(returnArg, funcBody) {
105
+ if (returnArg.type !== utils_1.AST_NODE_TYPES.Identifier)
106
+ return null;
107
+ const varName = returnArg.name;
108
+ // Walk the statements in the function body looking for a single const/let
109
+ // initialisation whose id matches varName.
110
+ for (const stmt of funcBody.body) {
111
+ if (stmt.type !== utils_1.AST_NODE_TYPES.VariableDeclaration)
112
+ continue;
113
+ for (const decl of stmt.declarations) {
114
+ if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
115
+ decl.id.name === varName &&
116
+ decl.init &&
117
+ isObjectExpression(decl.init)) {
118
+ return decl.init;
119
+ }
120
+ }
121
+ }
122
+ return null;
123
+ }
124
+ // Report nested-object properties in a data object (the actual propagation
125
+ // map, after afterData unwrapping). A property is flagged when:
126
+ // - its key is a simple non-dot-notation identifier/string
127
+ // - its value is an ObjectExpression
128
+ function checkDataObject(dataObj, report) {
129
+ for (const prop of dataObj.properties) {
130
+ if (!isProperty(prop))
131
+ continue;
132
+ if (isComputedKey(prop))
133
+ continue;
134
+ const keyName = getStaticKeyName(prop);
135
+ if (keyName === null)
136
+ continue;
137
+ // Dot-notation string keys represent leaf paths — their values may be
138
+ // any shape (the value IS the leaf data).
139
+ if (isDotNotationKey(keyName))
140
+ continue;
141
+ // Flag when the value is a nested object literal.
142
+ if (isObjectExpression(prop.value)) {
143
+ report(prop);
144
+ }
145
+ }
146
+ }
147
+ // Analyse a function body (BlockStatement) for return statements that produce
148
+ // nested objects, and report each violation.
149
+ function analyzeBlockBody(body, report) {
150
+ for (const stmt of body.body) {
151
+ if (stmt.type !== utils_1.AST_NODE_TYPES.ReturnStatement)
152
+ continue;
153
+ if (!stmt.argument)
154
+ continue;
155
+ let retObj = null;
156
+ if (isObjectExpression(stmt.argument)) {
157
+ retObj = stmt.argument;
158
+ }
159
+ else {
160
+ // Try single-binding pattern: const x = {...}; return x;
161
+ retObj = resolveVariableBinding(stmt.argument, body);
162
+ }
163
+ if (!retObj)
164
+ continue;
165
+ const dataObj = getDataObject(retObj);
166
+ if (!dataObj)
167
+ continue;
168
+ checkDataObject(dataObj, report);
169
+ }
170
+ }
171
+ exports.preferFlatTransformEachKeys = (0, createRule_1.createRule)({
172
+ name: 'prefer-flat-transform-each-keys',
173
+ meta: {
174
+ type: 'suggestion',
175
+ docs: {
176
+ description: 'Warn when transformEach in a propagation strategy returns nested object values instead of flat dot-notation keys.',
177
+ recommended: 'warn',
178
+ },
179
+ fixable: undefined,
180
+ schema: [],
181
+ messages: {
182
+ preferFlatTransformEachKeys: "Avoid returning nested objects from transformEach. Use flat dot-notation keys instead (e.g., 'parent.child.key' instead of { parent: { child: { key } } }). Nested objects cause FieldValue.delete() at the parent key on source deletion, wiping the entire sub-tree instead of just the propagated fields. If nesting is intentional (e.g., wiping an entire sub-tree), disable this rule with a comment explaining why.",
183
+ },
184
+ },
185
+ defaultOptions: [],
186
+ create(context) {
187
+ // Core detection is purely syntactic and works without type info.
188
+ // The parserServices guard below mirrors no-entire-object-hook-deps.ts so
189
+ // the rule degrades gracefully in test environments without parserOptions.project.
190
+ // Currently only the syntactic path is exercised; type-aware enhancement
191
+ // (e.g. resolving identifier types) can be wired in here when needed.
192
+ void context.parserServices; // accessed for future type-aware enhancement
193
+ return {
194
+ // Visit every ObjectExpression. If it looks like a propagation strategy,
195
+ // find the transformEach function and check its return values.
196
+ ObjectExpression(node) {
197
+ if (!isStrategyObject(node))
198
+ return;
199
+ if (usesResolveSelf(node))
200
+ return;
201
+ const transformEachProp = findProp(node, 'transformEach');
202
+ if (!transformEachProp)
203
+ return;
204
+ const fn = transformEachProp.value;
205
+ const report = (violatingNode) => {
206
+ context.report({
207
+ node: violatingNode,
208
+ messageId: 'preferFlatTransformEachKeys',
209
+ });
210
+ };
211
+ if (fn.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
212
+ fn.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
213
+ const body = fn.body;
214
+ if (isObjectExpression(body)) {
215
+ // Arrow function with implicit return: () => ({ ... })
216
+ const dataObj = getDataObject(body);
217
+ if (dataObj)
218
+ checkDataObject(dataObj, report);
219
+ }
220
+ else if (body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
221
+ analyzeBlockBody(body, report);
222
+ }
223
+ }
224
+ },
225
+ };
226
+ },
227
+ });
228
+ //# sourceMappingURL=prefer-flat-transform-each-keys.js.map
@@ -1,6 +1,7 @@
1
1
  type OptionShape = {
2
2
  stripPrefixes?: string[];
3
3
  ignoredMethods?: string[];
4
+ factoryMethods?: string[];
4
5
  ignoreAsync?: boolean;
5
6
  ignoreVoidReturn?: boolean;
6
7
  ignoreAbstract?: boolean;
@@ -34,10 +34,17 @@ const DEFAULT_IGNORED_METHODS = [
34
34
  'parse',
35
35
  'stringify',
36
36
  ];
37
+ /**
38
+ * Builder/factory terminal methods whose external callers (in other files)
39
+ * would break if converted to getters. These are imperative actions, not
40
+ * computed properties — per origin issue #990 edge case #4.
41
+ */
42
+ const DEFAULT_FACTORY_METHODS = ['build', 'create', 'make'];
37
43
  const SIDE_EFFECT_TAGS = ['sideEffect', 'sideEffects', 'mutates'];
38
44
  const DEFAULT_OPTIONS = {
39
45
  stripPrefixes: DEFAULT_PREFIXES,
40
46
  ignoredMethods: DEFAULT_IGNORED_METHODS,
47
+ factoryMethods: DEFAULT_FACTORY_METHODS,
41
48
  ignoreAsync: true,
42
49
  ignoreVoidReturn: true,
43
50
  ignoreAbstract: true,
@@ -166,6 +173,10 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
166
173
  type: 'array',
167
174
  items: { type: 'string' },
168
175
  },
176
+ factoryMethods: {
177
+ type: 'array',
178
+ items: { type: 'string' },
179
+ },
169
180
  ignoreAsync: {
170
181
  type: 'boolean',
171
182
  },
@@ -199,8 +210,10 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
199
210
  ...userOptions,
200
211
  stripPrefixes: userOptions.stripPrefixes ?? DEFAULT_OPTIONS.stripPrefixes,
201
212
  ignoredMethods: userOptions.ignoredMethods ?? DEFAULT_OPTIONS.ignoredMethods,
213
+ factoryMethods: userOptions.factoryMethods ?? DEFAULT_OPTIONS.factoryMethods,
202
214
  };
203
215
  const ignoredMethods = new Set(config.ignoredMethods);
216
+ const factoryMethods = new Set(config.factoryMethods);
204
217
  const prefixList = config.stripPrefixes;
205
218
  const sourceCode = context.getSourceCode();
206
219
  const ignoredTraversalKeys = new Set(['parent', 'loc', 'range']);
@@ -357,6 +370,28 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
357
370
  }
358
371
  return false;
359
372
  }
373
+ /**
374
+ * Returns true when the method body contains a ThrowStatement that is
375
+ * directly in the method's own scope (not inside a nested function/arrow).
376
+ * A method that can throw at the top level is an imperative assertion, not
377
+ * a computed property, so it must not become a getter.
378
+ */
379
+ function containsTopLevelThrow(body) {
380
+ const stack = [...body.body];
381
+ while (stack.length) {
382
+ const current = stack.pop();
383
+ // Do not descend into nested function-like scopes — a throw there is
384
+ // that nested function's concern, not the method's.
385
+ if (isFunctionLikeNode(current)) {
386
+ continue;
387
+ }
388
+ if (current.type === utils_1.AST_NODE_TYPES.ThrowStatement) {
389
+ return true;
390
+ }
391
+ pushChildNodes(current, stack);
392
+ }
393
+ return false;
394
+ }
360
395
  function suggestName(name) {
361
396
  for (const prefix of prefixList) {
362
397
  if (name.startsWith(prefix) &&
@@ -562,6 +597,11 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
562
597
  const name = node.key.name;
563
598
  if (ignoredMethods.has(name))
564
599
  return;
600
+ // Factory/builder terminal methods are imperative actions (issue #990 #4).
601
+ // Exemption takes precedence over stripPrefixes so names like "build"
602
+ // are never prefix-stripped into a getter candidate.
603
+ if (factoryMethods.has(name))
604
+ return;
565
605
  if (hasOverloadSignatures(node))
566
606
  return;
567
607
  const body = node.value.body;
@@ -572,6 +612,10 @@ exports.preferGetterOverParameterlessMethod = (0, createRule_1.createRule)({
572
612
  return;
573
613
  if (config.respectJsDocSideEffects && hasSideEffectTag(node))
574
614
  return;
615
+ // A method that throws at the top level is an imperative assertion or
616
+ // command, not a computed property — getters must be pure/non-throwing.
617
+ if (containsTopLevelThrow(body))
618
+ return;
575
619
  const sideEffectReason = analyzeMutations(body);
576
620
  const suggestedName = suggestName(name);
577
621
  candidates.push({ node, sideEffectReason, suggestedName });
@@ -0,0 +1,5 @@
1
+ type Options = [{
2
+ minFields?: number;
3
+ }];
4
+ export declare const preferSpreadOverReassembly: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"preferSpread", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
5
+ export {};