@blumintinc/eslint-plugin-blumint 1.16.1 → 1.17.0

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/logical-top-to-bottom-grouping.js +87 -19
  15. package/lib/rules/no-direct-function-state.d.ts +8 -0
  16. package/lib/rules/no-direct-function-state.js +285 -0
  17. package/lib/rules/no-fill-template-mutation.d.ts +1 -0
  18. package/lib/rules/no-fill-template-mutation.js +324 -0
  19. package/lib/rules/no-hungarian.js +11 -0
  20. package/lib/rules/no-portal-inside-tooltip.d.ts +10 -0
  21. package/lib/rules/no-portal-inside-tooltip.js +219 -0
  22. package/lib/rules/no-redundant-boolean-callback-props.d.ts +11 -0
  23. package/lib/rules/no-redundant-boolean-callback-props.js +355 -0
  24. package/lib/rules/no-satisfies-in-frontend-bundle.d.ts +8 -0
  25. package/lib/rules/no-satisfies-in-frontend-bundle.js +126 -0
  26. package/lib/rules/no-single-dismiss-dialog-button.d.ts +7 -0
  27. package/lib/rules/no-single-dismiss-dialog-button.js +127 -0
  28. package/lib/rules/no-stablehash-react-nodes.d.ts +1 -0
  29. package/lib/rules/no-stablehash-react-nodes.js +325 -0
  30. package/lib/rules/parallelize-loop-awaits.d.ts +8 -0
  31. package/lib/rules/parallelize-loop-awaits.js +582 -0
  32. package/lib/rules/prefer-flat-transform-each-keys.d.ts +1 -0
  33. package/lib/rules/prefer-flat-transform-each-keys.js +228 -0
  34. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +1 -0
  35. package/lib/rules/prefer-getter-over-parameterless-method.js +44 -0
  36. package/lib/rules/prefer-spread-over-reassembly.d.ts +5 -0
  37. package/lib/rules/prefer-spread-over-reassembly.js +401 -0
  38. package/lib/rules/prefer-sx-prop-over-system-props.d.ts +9 -0
  39. package/lib/rules/prefer-sx-prop-over-system-props.js +401 -0
  40. package/lib/rules/prefer-use-base62-id.d.ts +8 -0
  41. package/lib/rules/prefer-use-base62-id.js +483 -0
  42. package/lib/rules/prefer-use-theme.d.ts +1 -0
  43. package/lib/rules/prefer-use-theme.js +206 -0
  44. package/lib/rules/prefer-utility-function-own-file.d.ts +9 -0
  45. package/lib/rules/prefer-utility-function-own-file.js +505 -0
  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,325 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noStablehashReactNodes = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ /**
7
+ * Module path suffix that identifies the stableHash family of utilities.
8
+ * Matches both absolute and relative import paths that end in util/hash/stableHash.
9
+ */
10
+ const HASH_MODULE_SUFFIX = 'util/hash/stableHash';
11
+ /**
12
+ * Named exports from the hash module that we care about. sortedHash is included
13
+ * because it wraps stableHash and has the same stringification danger.
14
+ */
15
+ const HASH_EXPORT_NAMES = new Set(['stableHash', 'sortedHash']);
16
+ /**
17
+ * Type names that unambiguously denote React render trees or BluMint node
18
+ * wrappers. When a variable/parameter carries one of these annotations we know
19
+ * it is a ReactNode and must not be hashed.
20
+ */
21
+ const REACT_NODE_TYPE_NAMES = new Set([
22
+ 'ReactNode',
23
+ 'ReactElement',
24
+ 'JSX',
25
+ 'KeyedNode',
26
+ 'OrNode',
27
+ ]);
28
+ /**
29
+ * Object-literal property names whose presence signals the object contains a
30
+ * React render tree (either a children prop or a BluMint "Node" field).
31
+ */
32
+ const NODE_PROP_NAMES = new Set(['children', 'Node']);
33
+ // ---------------------------------------------------------------------------
34
+ // Helpers
35
+ // ---------------------------------------------------------------------------
36
+ /**
37
+ * Return true when the import source path ends with the known hash module
38
+ * suffix, handling both absolute paths (functions/src/util/hash/stableHash)
39
+ * and relative variants (../../../util/hash/stableHash).
40
+ */
41
+ function isHashModulePath(source) {
42
+ return source.endsWith(HASH_MODULE_SUFFIX);
43
+ }
44
+ /**
45
+ * Resolve the TSTypeAnnotation / TSTypeReference down to a base type name so
46
+ * we can match against REACT_NODE_TYPE_NAMES without depending on the type
47
+ * checker.
48
+ *
49
+ * Handles:
50
+ * ReactNode → 'ReactNode'
51
+ * React.ReactNode → 'ReactNode' (qualified reference)
52
+ * JSX.Element → 'JSX'
53
+ * OrNode<T> → 'OrNode'
54
+ * KeyedNode[] / readonly KeyedNode[] → 'KeyedNode' (array element)
55
+ */
56
+ function extractBaseTypeName(typeNode) {
57
+ switch (typeNode.type) {
58
+ case utils_1.AST_NODE_TYPES.TSTypeReference: {
59
+ const name = typeNode.typeName;
60
+ if (name.type === utils_1.AST_NODE_TYPES.Identifier) {
61
+ return name.name;
62
+ }
63
+ // Qualified name: React.ReactNode → take the right-hand part
64
+ if (name.type === utils_1.AST_NODE_TYPES.TSQualifiedName &&
65
+ name.right.type === utils_1.AST_NODE_TYPES.Identifier) {
66
+ // For JSX.Element we want "JSX" (the left) so we can match the set
67
+ if (name.left.type === utils_1.AST_NODE_TYPES.Identifier &&
68
+ name.left.name === 'JSX') {
69
+ return 'JSX';
70
+ }
71
+ return name.right.name;
72
+ }
73
+ return null;
74
+ }
75
+ case utils_1.AST_NODE_TYPES.TSArrayType:
76
+ return extractBaseTypeName(typeNode.elementType);
77
+ case utils_1.AST_NODE_TYPES.TSTypeOperator:
78
+ // readonly T[]
79
+ if (typeNode.typeAnnotation) {
80
+ return extractBaseTypeName(typeNode.typeAnnotation);
81
+ }
82
+ return null;
83
+ default:
84
+ return null;
85
+ }
86
+ }
87
+ /**
88
+ * Return true when a TS type annotation (already unwrapped to the inner type
89
+ * node) indicates a ReactNode / ReactElement / KeyedNode / OrNode / JSX.Element.
90
+ */
91
+ function isReactNodeTypeAnnotation(typeNode) {
92
+ const base = extractBaseTypeName(typeNode);
93
+ if (base === null)
94
+ return false;
95
+ return REACT_NODE_TYPE_NAMES.has(base);
96
+ }
97
+ /**
98
+ * Walk the program body's import declarations to build a mapping from local
99
+ * binding name → { isNamespace: boolean }.
100
+ *
101
+ * isNamespace = true → `import * as Hash from '...'` — we flag Hash.stableHash(...)
102
+ * isNamespace = false → `import { stableHash } from '...'` (possibly aliased)
103
+ */
104
+ function collectHashBindings(program) {
105
+ const bindings = new Map();
106
+ for (const node of program.body) {
107
+ if (node.type !== utils_1.AST_NODE_TYPES.ImportDeclaration)
108
+ continue;
109
+ if (!isHashModulePath(String(node.source.value)))
110
+ continue;
111
+ for (const spec of node.specifiers) {
112
+ if (spec.type === utils_1.AST_NODE_TYPES.ImportNamespaceSpecifier) {
113
+ // import * as Hash from '...'
114
+ bindings.set(spec.local.name, { isNamespace: true });
115
+ }
116
+ else if (spec.type === utils_1.AST_NODE_TYPES.ImportSpecifier) {
117
+ // import { stableHash } or import { stableHash as hash }
118
+ const importedName = spec.imported.type === utils_1.AST_NODE_TYPES.Identifier
119
+ ? spec.imported.name
120
+ : spec.imported.value;
121
+ if (HASH_EXPORT_NAMES.has(importedName)) {
122
+ bindings.set(spec.local.name, { isNamespace: false });
123
+ }
124
+ }
125
+ }
126
+ }
127
+ return bindings;
128
+ }
129
+ /**
130
+ * Return true when the argument to a stableHash call is a JSX element or
131
+ * JSX fragment.
132
+ */
133
+ function isJsxNode(node) {
134
+ return (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
135
+ node.type === utils_1.AST_NODE_TYPES.JSXFragment);
136
+ }
137
+ /**
138
+ * Return true when the argument is an object literal that contains a property
139
+ * named `children` or `Node` (shorthand or keyed), signalling it wraps a
140
+ * render-tree value.
141
+ */
142
+ function objectLiteralContainsNodeProp(node) {
143
+ for (const prop of node.properties) {
144
+ if (prop.type !== utils_1.AST_NODE_TYPES.Property)
145
+ continue;
146
+ const key = prop.key;
147
+ let propName = null;
148
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
149
+ propName = key.name;
150
+ }
151
+ else if (key.type === utils_1.AST_NODE_TYPES.Literal &&
152
+ typeof key.value === 'string') {
153
+ propName = key.value;
154
+ }
155
+ if (propName !== null && NODE_PROP_NAMES.has(propName)) {
156
+ return true;
157
+ }
158
+ }
159
+ return false;
160
+ }
161
+ /**
162
+ * Attempt to resolve the type annotation of an identifier from the local
163
+ * scope without the type checker.
164
+ *
165
+ * Strategy (in order):
166
+ * 1. The identifier is a parameter of the immediately enclosing function with
167
+ * a TSTypeAnnotation — read it directly.
168
+ * 2. The identifier is declared as a variable with a type annotation
169
+ * (`const x: ReactNode = ...`).
170
+ *
171
+ * Returns the TSTypeAnnotation typeAnnotation node or null if not found.
172
+ */
173
+ function resolveAnnotationForIdentifier(identNode) {
174
+ // Walk parents looking for a parameter list or variable declarator that
175
+ // matches the identifier's name.
176
+ let current = identNode.parent;
177
+ while (current) {
178
+ // Function or arrow-function parameter
179
+ if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
180
+ current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
181
+ current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression) {
182
+ const fn = current;
183
+ for (const param of fn.params) {
184
+ if (param.type === utils_1.AST_NODE_TYPES.Identifier &&
185
+ param.name === identNode.name &&
186
+ param.typeAnnotation?.typeAnnotation) {
187
+ return param.typeAnnotation.typeAnnotation;
188
+ }
189
+ // Handle default param: (node: KeyedNode = ...)
190
+ if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern &&
191
+ param.left.type === utils_1.AST_NODE_TYPES.Identifier &&
192
+ param.left.name === identNode.name &&
193
+ param.left.typeAnnotation?.typeAnnotation) {
194
+ const annotation = param.left.typeAnnotation;
195
+ if (annotation) {
196
+ return annotation.typeAnnotation;
197
+ }
198
+ }
199
+ }
200
+ }
201
+ // Variable declaration in the same scope
202
+ if (current.type === utils_1.AST_NODE_TYPES.VariableDeclaration) {
203
+ for (const decl of current.declarations) {
204
+ if (decl.id.type === utils_1.AST_NODE_TYPES.Identifier &&
205
+ decl.id.name === identNode.name &&
206
+ decl.id.typeAnnotation?.typeAnnotation) {
207
+ return decl.id.typeAnnotation.typeAnnotation;
208
+ }
209
+ }
210
+ }
211
+ current = current.parent;
212
+ }
213
+ return null;
214
+ }
215
+ /**
216
+ * Return true when the argument expression is, or contains, a ReactNode based
217
+ * on purely syntactic (AST-level) evidence:
218
+ *
219
+ * 1. Literal JSX element/fragment.
220
+ * 2. Object literal with a `children` or `Node` property.
221
+ * 3. Identifier whose nearest resolvable TS type annotation names a ReactNode.
222
+ */
223
+ function argIsReactNode(arg) {
224
+ // Strip as/type-assertion wrappers
225
+ let expr = arg;
226
+ while (expr.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
227
+ expr.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
228
+ expr.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
229
+ expr = expr.expression;
230
+ }
231
+ // Case 1: direct JSX
232
+ if (isJsxNode(expr)) {
233
+ return true;
234
+ }
235
+ // Case 2: object literal with node-shaped prop
236
+ if (expr.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
237
+ objectLiteralContainsNodeProp(expr)) {
238
+ return true;
239
+ }
240
+ // Case 3: identifier with resolvable ReactNode annotation
241
+ if (expr.type === utils_1.AST_NODE_TYPES.Identifier) {
242
+ const typeNode = resolveAnnotationForIdentifier(expr);
243
+ if (typeNode && isReactNodeTypeAnnotation(typeNode)) {
244
+ return true;
245
+ }
246
+ }
247
+ return false;
248
+ }
249
+ // ---------------------------------------------------------------------------
250
+ // Rule
251
+ // ---------------------------------------------------------------------------
252
+ exports.noStablehashReactNodes = (0, createRule_1.createRule)({
253
+ name: 'no-stablehash-react-nodes',
254
+ meta: {
255
+ type: 'problem',
256
+ docs: {
257
+ description: 'Prevent passing ReactNodes or render-tree values to stableHash(), which deep-stringifies its argument and can freeze the browser.',
258
+ recommended: 'error',
259
+ },
260
+ fixable: undefined,
261
+ schema: [],
262
+ messages: {
263
+ noStableHashReactNode: 'Do not stableHash ReactNodes / KeyedNodes — stableHash() stringifies the full render tree and can freeze the browser. Hash stable keys/ids instead (e.g. stableHash(nodes.map((n) => n.key))).',
264
+ },
265
+ },
266
+ defaultOptions: [],
267
+ create(context) {
268
+ // Collect hash-function bindings lazily on first CallExpression visit.
269
+ let bindings = null;
270
+ function getBindings() {
271
+ if (bindings === null) {
272
+ const sourceCode = context
273
+ .sourceCode ?? context.getSourceCode();
274
+ bindings = collectHashBindings(sourceCode.ast);
275
+ }
276
+ return bindings;
277
+ }
278
+ /**
279
+ * Determine whether a CallExpression's callee is a tracked stableHash /
280
+ * sortedHash binding and return the first argument if so.
281
+ */
282
+ function getTrackedCallArg(node) {
283
+ if (node.arguments.length === 0)
284
+ return null;
285
+ const map = getBindings();
286
+ if (map.size === 0)
287
+ return null;
288
+ const callee = node.callee;
289
+ // Direct call: stableHash(arg) or hash(arg) (aliased)
290
+ if (callee.type === utils_1.AST_NODE_TYPES.Identifier) {
291
+ const info = map.get(callee.name);
292
+ if (info && !info.isNamespace) {
293
+ return node.arguments[0];
294
+ }
295
+ }
296
+ // Member expression: Hash.stableHash(arg)
297
+ if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
298
+ !callee.computed &&
299
+ callee.object.type === utils_1.AST_NODE_TYPES.Identifier &&
300
+ callee.property.type === utils_1.AST_NODE_TYPES.Identifier) {
301
+ const objectName = callee.object.name;
302
+ const methodName = callee.property.name;
303
+ const info = map.get(objectName);
304
+ if (info && info.isNamespace && HASH_EXPORT_NAMES.has(methodName)) {
305
+ return node.arguments[0];
306
+ }
307
+ }
308
+ return null;
309
+ }
310
+ return {
311
+ CallExpression(node) {
312
+ const arg = getTrackedCallArg(node);
313
+ if (arg === null)
314
+ return;
315
+ if (argIsReactNode(arg)) {
316
+ context.report({
317
+ node,
318
+ messageId: 'noStableHashReactNode',
319
+ });
320
+ }
321
+ },
322
+ };
323
+ },
324
+ });
325
+ //# sourceMappingURL=no-stablehash-react-nodes.js.map
@@ -0,0 +1,8 @@
1
+ type Options = [
2
+ {
3
+ coordinatorPatterns?: string[];
4
+ rateLimitedPatterns?: string[];
5
+ }
6
+ ];
7
+ export declare const parallelizeLoopAwaits: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"parallelizeLoopAwaits", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
8
+ export {};