@blumintinc/eslint-plugin-blumint 1.13.0 → 1.14.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 (186) hide show
  1. package/README.md +170 -139
  2. package/lib/index.js +190 -1
  3. package/lib/rules/array-methods-this-context.js +1 -1
  4. package/lib/rules/class-methods-read-top-to-bottom.js +24 -5
  5. package/lib/rules/consistent-callback-naming.js +1 -1
  6. package/lib/rules/dynamic-https-errors.js +3 -4
  7. package/lib/rules/enforce-assert-safe-object-key.js +11 -11
  8. package/lib/rules/enforce-boolean-naming-prefixes.js +11 -23
  9. package/lib/rules/enforce-callback-memo.js +1 -1
  10. package/lib/rules/enforce-centralized-mock-firestore.js +1 -1
  11. package/lib/rules/enforce-console-error.js +35 -7
  12. package/lib/rules/enforce-dynamic-file-naming.d.ts +2 -0
  13. package/lib/rules/enforce-dynamic-file-naming.js +53 -28
  14. package/lib/rules/enforce-early-destructuring.d.ts +2 -0
  15. package/lib/rules/enforce-early-destructuring.js +980 -0
  16. package/lib/rules/enforce-empty-object-check.d.ts +11 -0
  17. package/lib/rules/enforce-empty-object-check.js +502 -0
  18. package/lib/rules/enforce-exported-function-types.js +1 -1
  19. package/lib/rules/enforce-fieldpath-syntax-in-docsetter.js +100 -50
  20. package/lib/rules/enforce-firestore-doc-ref-generic.js +15 -3
  21. package/lib/rules/enforce-firestore-facade.js +4 -2
  22. package/lib/rules/enforce-firestore-set-merge.js +3 -4
  23. package/lib/rules/enforce-global-constants.js +1 -1
  24. package/lib/rules/enforce-identifiable-firestore-type.js +108 -58
  25. package/lib/rules/enforce-memoize-async.js +2 -6
  26. package/lib/rules/enforce-memoize-getters.js +39 -15
  27. package/lib/rules/enforce-microdiff.js +1 -1
  28. package/lib/rules/enforce-object-literal-as-const.js +4 -4
  29. package/lib/rules/enforce-positive-naming.js +4 -0
  30. package/lib/rules/enforce-props-argument-name.js +2 -2
  31. package/lib/rules/enforce-safe-stringify.js +1 -1
  32. package/lib/rules/enforce-serializable-params.js +3 -3
  33. package/lib/rules/enforce-singular-type-names.js +1 -1
  34. package/lib/rules/enforce-stable-hash-spread-props.d.ts +13 -0
  35. package/lib/rules/enforce-stable-hash-spread-props.js +342 -0
  36. package/lib/rules/enforce-storage-context.d.ts +9 -0
  37. package/lib/rules/enforce-storage-context.js +646 -0
  38. package/lib/rules/enforce-timestamp-now.js +23 -7
  39. package/lib/rules/enforce-transform-memoization.d.ts +4 -0
  40. package/lib/rules/enforce-transform-memoization.js +416 -0
  41. package/lib/rules/enforce-typescript-markdown-code-blocks.js +7 -3
  42. package/lib/rules/enforce-unique-cursor-headers.d.ts +16 -0
  43. package/lib/rules/enforce-unique-cursor-headers.js +365 -0
  44. package/lib/rules/enforce-verb-noun-naming.js +5 -4
  45. package/lib/rules/ensure-pointer-events-none.js +28 -2
  46. package/lib/rules/export-if-in-doubt.js +27 -4
  47. package/lib/rules/extract-global-constants.js +40 -13
  48. package/lib/rules/fast-deep-equal-over-microdiff.js +29 -39
  49. package/lib/rules/firestore-transaction-reads-before-writes.js +50 -4
  50. package/lib/rules/flatten-push-calls.d.ts +2 -0
  51. package/lib/rules/flatten-push-calls.js +428 -0
  52. package/lib/rules/global-const-style.js +50 -19
  53. package/lib/rules/jsdoc-above-field.d.ts +11 -0
  54. package/lib/rules/jsdoc-above-field.js +208 -0
  55. package/lib/rules/key-only-outermost-element.js +21 -3
  56. package/lib/rules/logical-top-to-bottom-grouping.d.ts +5 -0
  57. package/lib/rules/logical-top-to-bottom-grouping.js +1258 -0
  58. package/lib/rules/memo-compare-deeply-complex-props.d.ts +3 -0
  59. package/lib/rules/memo-compare-deeply-complex-props.js +788 -0
  60. package/lib/rules/memo-nested-react-components.d.ts +8 -0
  61. package/lib/rules/memo-nested-react-components.js +410 -0
  62. package/lib/rules/no-always-true-false-conditions.d.ts +2 -1
  63. package/lib/rules/no-always-true-false-conditions.js +68 -8
  64. package/lib/rules/no-array-length-in-deps.js +9 -3
  65. package/lib/rules/no-async-foreach.js +210 -12
  66. package/lib/rules/no-circular-references.js +18 -17
  67. package/lib/rules/no-class-instance-destructuring.js +55 -12
  68. package/lib/rules/no-complex-cloud-params.js +7 -3
  69. package/lib/rules/no-compositing-layer-props.js +2 -2
  70. package/lib/rules/no-conditional-literals-in-jsx.js +37 -12
  71. package/lib/rules/no-console-error.d.ts +9 -0
  72. package/lib/rules/no-console-error.js +525 -0
  73. package/lib/rules/no-curly-brackets-around-commented-properties.d.ts +2 -0
  74. package/lib/rules/no-curly-brackets-around-commented-properties.js +258 -0
  75. package/lib/rules/no-empty-dependency-use-callbacks.d.ts +11 -0
  76. package/lib/rules/no-empty-dependency-use-callbacks.js +576 -0
  77. package/lib/rules/no-entire-object-hook-deps.js +4 -6
  78. package/lib/rules/no-excessive-parent-chain.js +1 -1
  79. package/lib/rules/no-explicit-return-type.d.ts +2 -1
  80. package/lib/rules/no-explicit-return-type.js +215 -26
  81. package/lib/rules/no-filter-without-return.js +5 -1
  82. package/lib/rules/no-firestore-jest-mock.d.ts +2 -1
  83. package/lib/rules/no-firestore-jest-mock.js +126 -8
  84. package/lib/rules/no-firestore-object-arrays.js +67 -12
  85. package/lib/rules/no-handler-suffix.d.ts +12 -0
  86. package/lib/rules/no-handler-suffix.js +305 -0
  87. package/lib/rules/no-hungarian.js +1 -1
  88. package/lib/rules/no-inline-component-prop.d.ts +10 -0
  89. package/lib/rules/no-inline-component-prop.js +456 -0
  90. package/lib/rules/no-jsx-in-hooks.js +6 -1
  91. package/lib/rules/no-jsx-whitespace-literal.js +8 -2
  92. package/lib/rules/no-margin-properties.js +6 -6
  93. package/lib/rules/no-memoize-on-static.js +9 -1
  94. package/lib/rules/no-misleading-boolean-prefixes.js +7 -3
  95. package/lib/rules/no-misused-switch-case.js +22 -1
  96. package/lib/rules/no-mixed-firestore-transactions.d.ts +3 -1
  97. package/lib/rules/no-mixed-firestore-transactions.js +296 -34
  98. package/lib/rules/no-mock-firebase-admin.js +5 -2
  99. package/lib/rules/no-object-values-on-strings.js +10 -2
  100. package/lib/rules/no-overridable-method-calls-in-constructor.js +29 -17
  101. package/lib/rules/no-passthrough-getters.js +38 -2
  102. package/lib/rules/no-redundant-annotation-assertion.d.ts +2 -0
  103. package/lib/rules/no-redundant-annotation-assertion.js +402 -0
  104. package/lib/rules/no-redundant-param-types.js +16 -6
  105. package/lib/rules/no-redundant-this-params.d.ts +3 -0
  106. package/lib/rules/no-redundant-this-params.js +459 -0
  107. package/lib/rules/no-redundant-usecallback-wrapper.js +16 -3
  108. package/lib/rules/no-res-error-status-in-onrequest.d.ts +4 -0
  109. package/lib/rules/no-res-error-status-in-onrequest.js +521 -0
  110. package/lib/rules/no-restricted-properties-fix.js +11 -10
  111. package/lib/rules/no-separate-loading-state.js +7 -16
  112. package/lib/rules/no-stale-state-across-await.js +1 -1
  113. package/lib/rules/no-type-assertion-returns.js +43 -49
  114. package/lib/rules/no-undefined-null-passthrough.js +61 -31
  115. package/lib/rules/no-unmemoized-memo-without-props.d.ts +8 -0
  116. package/lib/rules/no-unmemoized-memo-without-props.js +426 -0
  117. package/lib/rules/no-unnecessary-destructuring-rename.d.ts +2 -0
  118. package/lib/rules/no-unnecessary-destructuring-rename.js +347 -0
  119. package/lib/rules/no-unnecessary-destructuring.js +14 -4
  120. package/lib/rules/no-unnecessary-verb-suffix.js +2 -1
  121. package/lib/rules/no-unpinned-dependencies.js +36 -5
  122. package/lib/rules/no-unused-props.d.ts +2 -2
  123. package/lib/rules/no-unused-props.js +295 -91
  124. package/lib/rules/no-unused-usestate.js +6 -2
  125. package/lib/rules/no-useless-fragment.js +28 -2
  126. package/lib/rules/no-useless-usememo-primitives.d.ts +9 -0
  127. package/lib/rules/no-useless-usememo-primitives.js +393 -0
  128. package/lib/rules/no-usememo-for-pass-by-value.d.ts +13 -0
  129. package/lib/rules/no-usememo-for-pass-by-value.js +757 -0
  130. package/lib/rules/no-uuidv4-base62-as-key.js +18 -13
  131. package/lib/rules/omit-index-html.d.ts +2 -1
  132. package/lib/rules/omit-index-html.js +62 -7
  133. package/lib/rules/optimize-object-boolean-conditions.js +6 -4
  134. package/lib/rules/parallelize-async-operations.js +24 -5
  135. package/lib/rules/prefer-batch-operations.d.ts +1 -3
  136. package/lib/rules/prefer-batch-operations.js +32 -5
  137. package/lib/rules/prefer-block-comments-for-declarations.js +9 -4
  138. package/lib/rules/prefer-clone-deep.js +3 -3
  139. package/lib/rules/prefer-destructuring-no-class.d.ts +2 -1
  140. package/lib/rules/prefer-destructuring-no-class.js +244 -59
  141. package/lib/rules/prefer-docsetter-setall.d.ts +2 -0
  142. package/lib/rules/prefer-docsetter-setall.js +243 -0
  143. package/lib/rules/prefer-field-paths-in-transforms.js +4 -3
  144. package/lib/rules/prefer-fragment-component.js +1 -1
  145. package/lib/rules/prefer-fragment-shorthand.js +2 -1
  146. package/lib/rules/prefer-getter-over-parameterless-method.d.ts +13 -0
  147. package/lib/rules/prefer-getter-over-parameterless-method.js +648 -0
  148. package/lib/rules/prefer-global-router-state-key.js +88 -13
  149. package/lib/rules/prefer-memoized-props.d.ts +3 -0
  150. package/lib/rules/prefer-memoized-props.js +445 -0
  151. package/lib/rules/prefer-next-dynamic.js +60 -69
  152. package/lib/rules/prefer-nullish-coalescing-boolean-props.js +9 -4
  153. package/lib/rules/prefer-nullish-coalescing-override.d.ts +2 -1
  154. package/lib/rules/prefer-nullish-coalescing-override.js +11 -5
  155. package/lib/rules/prefer-params-over-parent-id.js +220 -171
  156. package/lib/rules/prefer-settings-object.js +2 -2
  157. package/lib/rules/prefer-type-over-interface.js +7 -2
  158. package/lib/rules/prefer-url-tostring-over-tojson.js +6 -3
  159. package/lib/rules/prefer-use-deep-compare-memo.js +8 -5
  160. package/lib/rules/prefer-usecallback-over-usememo-for-functions.js +1 -1
  161. package/lib/rules/prefer-utility-function-over-private-static.js +34 -2
  162. package/lib/rules/prevent-children-clobber.d.ts +2 -0
  163. package/lib/rules/prevent-children-clobber.js +536 -0
  164. package/lib/rules/react-memoize-literals.d.ts +4 -0
  165. package/lib/rules/react-memoize-literals.js +495 -0
  166. package/lib/rules/react-usememo-should-be-component.js +2 -2
  167. package/lib/rules/require-hooks-default-params.js +17 -16
  168. package/lib/rules/require-https-error-cause.d.ts +4 -0
  169. package/lib/rules/require-https-error-cause.js +136 -0
  170. package/lib/rules/require-https-error.js +43 -21
  171. package/lib/rules/require-image-optimized.js +1 -1
  172. package/lib/rules/require-memo.js +1 -1
  173. package/lib/rules/require-memoize-jsx-returners.d.ts +3 -0
  174. package/lib/rules/require-memoize-jsx-returners.js +485 -0
  175. package/lib/rules/require-usememo-object-literals.js +2 -3
  176. package/lib/rules/test-file-location-enforcement.js +1 -1
  177. package/lib/rules/use-latest-callback.js +6 -5
  178. package/lib/rules/vertically-group-related-functions.d.ts +16 -0
  179. package/lib/rules/vertically-group-related-functions.js +480 -0
  180. package/lib/utils/ASTHelpers.d.ts +13 -1
  181. package/lib/utils/ASTHelpers.js +24 -0
  182. package/lib/utils/getMethodName.d.ts +27 -0
  183. package/lib/utils/getMethodName.js +35 -0
  184. package/lib/utils/tsTypeClassifier.d.ts +30 -0
  185. package/lib/utils/tsTypeClassifier.js +149 -0
  186. package/package.json +3 -1
@@ -14,8 +14,8 @@ exports.default = (0, createRule_1.createRule)({
14
14
  fixable: 'code',
15
15
  schema: [],
16
16
  messages: {
17
- upperSnakeCase: 'Global constants should be in UPPER_SNAKE_CASE',
18
- asConst: 'Global constants should use "as const"',
17
+ upperSnakeCase: 'Global constant "{{name}}" should be written in UPPER_SNAKE_CASE (e.g., "{{suggestedName}}") so it reads as a module-level configuration value that never changes; rename it to make its immutability obvious.',
18
+ asConst: 'Global constant "{{name}}" is initialized with {{valueKind}} but lacks `as const`, so TypeScript widens the type and code can mutate it accidentally; append `as const` to freeze the value and preserve literal types.',
19
19
  },
20
20
  },
21
21
  defaultOptions: [],
@@ -23,6 +23,27 @@ exports.default = (0, createRule_1.createRule)({
23
23
  // Check if the file is a TypeScript file
24
24
  const isTypeScript = context.getFilename().endsWith('.ts') ||
25
25
  context.getFilename().endsWith('.tsx');
26
+ const unwrapAssertions = (node) => {
27
+ let target = node;
28
+ while (target.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
29
+ target.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
30
+ target = target.expression;
31
+ }
32
+ return target;
33
+ };
34
+ const describeValueKind = (node) => {
35
+ const target = unwrapAssertions(node);
36
+ if (target.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
37
+ return 'an array literal';
38
+ }
39
+ if (target.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
40
+ return 'an object literal';
41
+ }
42
+ if (target.type === utils_1.AST_NODE_TYPES.Literal) {
43
+ return 'a literal value';
44
+ }
45
+ return 'a value';
46
+ };
26
47
  return {
27
48
  VariableDeclaration(node) {
28
49
  // Only check top-level const declarations
@@ -87,7 +108,7 @@ exports.default = (0, createRule_1.createRule)({
87
108
  init.type === utils_1.AST_NODE_TYPES.NewExpression) {
88
109
  return;
89
110
  }
90
- const sourceCode = context.getSourceCode();
111
+ const sourceCode = context.sourceCode;
91
112
  const initText = sourceCode.getText(init);
92
113
  const typeAnnotation = declaration.id.typeAnnotation;
93
114
  const typeText = typeAnnotation
@@ -95,42 +116,48 @@ exports.default = (0, createRule_1.createRule)({
95
116
  : '';
96
117
  // Only check for as const in TypeScript files
97
118
  if (isTypeScript) {
98
- const isAsConstExpression = (node) => {
99
- if (node.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
100
- return (node.typeAnnotation?.type ===
101
- utils_1.AST_NODE_TYPES.TSTypeReference &&
102
- node.typeAnnotation?.typeName
103
- ?.name === 'const');
119
+ const hasAsConstAssertion = (node) => {
120
+ let current = node;
121
+ while (current &&
122
+ (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
123
+ current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion)) {
124
+ const { typeAnnotation } = current;
125
+ if (typeAnnotation?.type === utils_1.AST_NODE_TYPES.TSTypeReference &&
126
+ typeAnnotation.typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
127
+ typeAnnotation.typeName.name === 'const') {
128
+ return true;
129
+ }
130
+ current = current.expression;
104
131
  }
105
132
  return false;
106
133
  };
107
134
  const shouldHaveAsConst = (node) => {
108
135
  // Skip if it's already an as const expression
109
- if (isAsConstExpression(node)) {
136
+ if (hasAsConstAssertion(node)) {
110
137
  return false;
111
138
  }
112
- // Handle type assertions
113
- if (node.type === utils_1.AST_NODE_TYPES.TSTypeAssertion ||
114
- node.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
115
- return shouldHaveAsConst(node.expression);
116
- }
139
+ const target = unwrapAssertions(node);
117
140
  // Skip if there's an explicit type annotation
118
141
  if (declaration.id.typeAnnotation) {
119
142
  return false;
120
143
  }
121
144
  // Check if it's a literal, array, or object that should have as const
122
145
  // Skip regular expressions as they are already immutable
123
- if (node.type === utils_1.AST_NODE_TYPES.Literal && 'regex' in node) {
146
+ if (target.type === utils_1.AST_NODE_TYPES.Literal && 'regex' in target) {
124
147
  return false;
125
148
  }
126
- return (node.type === utils_1.AST_NODE_TYPES.Literal ||
127
- node.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
128
- node.type === utils_1.AST_NODE_TYPES.ObjectExpression);
149
+ return (target.type === utils_1.AST_NODE_TYPES.Literal ||
150
+ target.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
151
+ target.type === utils_1.AST_NODE_TYPES.ObjectExpression);
129
152
  };
130
153
  if (shouldHaveAsConst(init)) {
131
154
  context.report({
132
155
  node: declaration,
133
156
  messageId: 'asConst',
157
+ data: {
158
+ name,
159
+ valueKind: describeValueKind(init),
160
+ },
134
161
  fix(fixer) {
135
162
  return fixer.replaceText(init, `${initText} as const`);
136
163
  },
@@ -146,6 +173,10 @@ exports.default = (0, createRule_1.createRule)({
146
173
  context.report({
147
174
  node: declaration,
148
175
  messageId: 'upperSnakeCase',
176
+ data: {
177
+ name,
178
+ suggestedName: newName,
179
+ },
149
180
  fix(fixer) {
150
181
  if (typeAnnotation) {
151
182
  return fixer.replaceText(declaration, `${newName}${typeText} = ${initText}`);
@@ -0,0 +1,11 @@
1
+ type Options = [
2
+ {
3
+ /**
4
+ * When true, the rule also flags inline JSDoc on object literal properties.
5
+ * By default, only interface/type literals and class fields are checked.
6
+ */
7
+ checkObjectLiterals?: boolean;
8
+ }
9
+ ];
10
+ export declare const jsdocAboveField: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"moveJsdocAbove", Options, import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
11
+ export {};
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.jsdocAboveField = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const createRule_1 = require("../utils/createRule");
6
+ const defaultOptions = [{ checkObjectLiterals: false }];
7
+ exports.jsdocAboveField = (0, createRule_1.createRule)({
8
+ name: 'jsdoc-above-field',
9
+ meta: {
10
+ type: 'suggestion',
11
+ docs: {
12
+ description: 'Require JSDoc blocks to sit above fields instead of trailing inline so IDE hovers surface the documentation.',
13
+ recommended: 'error',
14
+ },
15
+ fixable: 'code',
16
+ schema: [
17
+ {
18
+ type: 'object',
19
+ properties: {
20
+ checkObjectLiterals: {
21
+ type: 'boolean',
22
+ description: 'Also enforce JSDoc placement for object literal properties.',
23
+ default: false,
24
+ },
25
+ },
26
+ additionalProperties: false,
27
+ },
28
+ ],
29
+ messages: {
30
+ moveJsdocAbove: 'Inline JSDoc for "{{name}}" sits after the {{kind}} → IDE hovers and autocomplete skip trailing inline JSDoc, so tags like @deprecated/@default never surface when developers hover → Move the JSDoc block above the {{kind}} (and above any decorators or modifiers) so the documentation stays visible where it is needed.',
31
+ },
32
+ },
33
+ defaultOptions,
34
+ create(context, [options = defaultOptions[0]]) {
35
+ const sourceCode = context.getSourceCode();
36
+ const { checkObjectLiterals = false } = options;
37
+ const allComments = sourceCode.getAllComments();
38
+ const isRelevantNode = (node) => {
39
+ if (node.type === utils_1.AST_NODE_TYPES.TSPropertySignature) {
40
+ return true;
41
+ }
42
+ if (node.type === utils_1.AST_NODE_TYPES.PropertyDefinition) {
43
+ return true;
44
+ }
45
+ if (checkObjectLiterals &&
46
+ node.type === utils_1.AST_NODE_TYPES.Property &&
47
+ node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
48
+ return true;
49
+ }
50
+ return false;
51
+ };
52
+ const isJSDocBlock = (comment) => comment.type === 'Block' && comment.value.startsWith('*');
53
+ const getPropertyName = (node) => {
54
+ const key = node.key;
55
+ if (key.type === utils_1.AST_NODE_TYPES.Identifier) {
56
+ return key.name;
57
+ }
58
+ if (key.type === utils_1.AST_NODE_TYPES.Literal &&
59
+ typeof key.value === 'string') {
60
+ return key.value;
61
+ }
62
+ return 'computed property';
63
+ };
64
+ const getKind = (node) => {
65
+ if (node.type === utils_1.AST_NODE_TYPES.PropertyDefinition) {
66
+ return 'class field';
67
+ }
68
+ if (node.type === utils_1.AST_NODE_TYPES.Property) {
69
+ return 'object property';
70
+ }
71
+ return 'type field';
72
+ };
73
+ const inlineJSDocOnSameLine = (node) => {
74
+ return allComments.find((comment) => {
75
+ if (!isJSDocBlock(comment)) {
76
+ return false;
77
+ }
78
+ if (comment.loc.start.line !== node.loc.end.line) {
79
+ return false;
80
+ }
81
+ if (comment.range[0] < node.range[1]) {
82
+ return false;
83
+ }
84
+ const between = sourceCode.text.slice(node.range[1], comment.range[0]);
85
+ return /^[\s;,]*$/.test(between);
86
+ });
87
+ };
88
+ const indentForNode = (node) => {
89
+ const line = sourceCode.lines[node.loc.start.line - 1] ?? '';
90
+ const beforeColumn = line.slice(0, node.loc.start.column);
91
+ const trailingWhitespace = beforeColumn.match(/\s*$/);
92
+ return trailingWhitespace?.[0] ?? '';
93
+ };
94
+ const formatCommentWithIndent = (comment, indent) => {
95
+ const rawText = sourceCode.getText(comment);
96
+ const rawLines = rawText.split('\n');
97
+ let minIndentAfterStar;
98
+ // Calculate minimum indentation across all lines (excluding the first line and standard closing line)
99
+ rawLines.slice(1).forEach((line, index, arr) => {
100
+ const isLastLine = index === arr.length - 1;
101
+ const starMatch = line.match(/^\s*\*(.*)$/);
102
+ if (!starMatch) {
103
+ return;
104
+ }
105
+ const afterStar = starMatch[1];
106
+ // Skip the standard " */" closing line
107
+ if (isLastLine && afterStar.trim() === '/') {
108
+ return;
109
+ }
110
+ const contentIndent = afterStar.match(/^([ \t]*)\S/);
111
+ if (contentIndent) {
112
+ const indentLength = contentIndent[1].length;
113
+ minIndentAfterStar =
114
+ minIndentAfterStar === undefined
115
+ ? indentLength
116
+ : Math.min(minIndentAfterStar, indentLength);
117
+ }
118
+ });
119
+ // Normalize indentation: we want the minimum indentation after '*' to be exactly 1 space.
120
+ // (e.g., if min indent is 3, we strip 2; if min indent is 0, we add 1).
121
+ const indentToAdjustment = minIndentAfterStar === undefined ? 0 : minIndentAfterStar - 1;
122
+ const normalize = (text) => {
123
+ if (indentToAdjustment > 0) {
124
+ return text.replace(new RegExp(`^[ \\t]{0,${indentToAdjustment}}`), '');
125
+ }
126
+ if (indentToAdjustment < 0) {
127
+ return ' '.repeat(-indentToAdjustment) + text;
128
+ }
129
+ return text;
130
+ };
131
+ const normalizedLines = rawLines.map((line, index) => {
132
+ if (index === 0) {
133
+ return line.trimStart();
134
+ }
135
+ const starMatch = line.match(/^\s*\*(.*)$/);
136
+ if (!starMatch) {
137
+ return line.trimStart();
138
+ }
139
+ const afterStar = normalize(starMatch[1]);
140
+ if (afterStar.trim() === '/') {
141
+ return ' */';
142
+ }
143
+ if (afterStar.trim() === '') {
144
+ return ' *';
145
+ }
146
+ const needsSpace = !afterStar.startsWith(' ') && !afterStar.startsWith('\t');
147
+ const content = needsSpace ? ` ${afterStar}` : afterStar;
148
+ return ` *${content}`;
149
+ });
150
+ return normalizedLines.map((line) => `${indent}${line}`).join('\n');
151
+ };
152
+ const reportInlineJSDoc = (node, comment) => {
153
+ const insertTarget = node.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
154
+ node.decorators &&
155
+ node.decorators.length > 0
156
+ ? node.decorators[0]
157
+ : node;
158
+ const indent = indentForNode(insertTarget);
159
+ const commentText = formatCommentWithIndent(comment, indent);
160
+ let removalStart = comment.range[0];
161
+ const removalEnd = comment.range[1];
162
+ const lineStart = insertTarget.range[0] - insertTarget.loc.start.column;
163
+ const textBeforeNode = sourceCode.text.slice(lineStart, insertTarget.range[0]);
164
+ const hasCodeBeforeNode = /\S/.test(textBeforeNode);
165
+ const insertionPoint = hasCodeBeforeNode
166
+ ? insertTarget.range[0]
167
+ : lineStart;
168
+ const insertionText = hasCodeBeforeNode
169
+ ? `\n${commentText}\n${indent}`
170
+ : `${commentText}\n`;
171
+ while (removalStart > node.range[1] &&
172
+ /\s/.test(sourceCode.text[removalStart - 1])) {
173
+ removalStart -= 1;
174
+ }
175
+ context.report({
176
+ node,
177
+ loc: comment.loc,
178
+ messageId: 'moveJsdocAbove',
179
+ data: {
180
+ name: getPropertyName(node),
181
+ kind: getKind(node),
182
+ },
183
+ fix(fixer) {
184
+ return [
185
+ fixer.insertTextBeforeRange([insertionPoint, insertionPoint], insertionText),
186
+ fixer.removeRange([removalStart, removalEnd]),
187
+ ];
188
+ },
189
+ });
190
+ };
191
+ const checkNode = (node) => {
192
+ if (!isRelevantNode(node)) {
193
+ return;
194
+ }
195
+ const jsdocComment = inlineJSDocOnSameLine(node);
196
+ if (!jsdocComment) {
197
+ return;
198
+ }
199
+ reportInlineJSDoc(node, jsdocComment);
200
+ };
201
+ return {
202
+ TSPropertySignature: checkNode,
203
+ PropertyDefinition: checkNode,
204
+ ...(checkObjectLiterals && { Property: checkNode }),
205
+ };
206
+ },
207
+ });
208
+ //# sourceMappingURL=jsdoc-above-field.js.map
@@ -2,6 +2,21 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.keyOnlyOutermostElement = void 0;
4
4
  const createRule_1 = require("../utils/createRule");
5
+ const getJSXElementName = (name) => {
6
+ if (name.type === 'JSXIdentifier') {
7
+ return name.name;
8
+ }
9
+ if (name.type === 'JSXMemberExpression') {
10
+ const objectName = name.object.type === 'JSXIdentifier'
11
+ ? name.object.name
12
+ : getJSXElementName(name.object);
13
+ return `${objectName}.${name.property.name}`;
14
+ }
15
+ if (name.type === 'JSXNamespacedName') {
16
+ return `${name.namespace.name}:${name.name.name}`;
17
+ }
18
+ return 'element';
19
+ };
5
20
  exports.keyOnlyOutermostElement = (0, createRule_1.createRule)({
6
21
  name: 'key-only-outermost-element',
7
22
  meta: {
@@ -11,8 +26,8 @@ exports.keyOnlyOutermostElement = (0, createRule_1.createRule)({
11
26
  recommended: 'error',
12
27
  },
13
28
  messages: {
14
- keyOnlyOutermostElement: 'Only the outermost element in a list rendering should have a key prop. Remove the key from this nested element.',
15
- fragmentShouldHaveKey: 'Fragment used as the outermost element in a list should use <React.Fragment key={...}> instead of shorthand syntax.',
29
+ keyOnlyOutermostElement: 'Nested element "{{elementName}}" has a key even though the list item already owns the identity. React reconciles list items using the key on the outermost element; nested keys create redundant identities and can mask ordering bugs. Remove this nested key and keep the key only on the element returned from map().',
30
+ fragmentShouldHaveKey: 'List items returned as fragments need a key on the fragment. Shorthand fragments (<></>) cannot accept keys, so React cannot track each item when the list reorders. Replace the shorthand with <React.Fragment key={...}> or another keyed wrapper so every list item has a stable identity.',
16
31
  },
17
32
  schema: [],
18
33
  fixable: 'code',
@@ -104,10 +119,13 @@ exports.keyOnlyOutermostElement = (0, createRule_1.createRule)({
104
119
  if (isNestedInMapCallback) {
105
120
  // Mark this attribute as reported to avoid duplicate reports
106
121
  reportedAttributes.add(attr);
107
- const sourceCode = context.getSourceCode();
122
+ const sourceCode = context.sourceCode;
108
123
  context.report({
109
124
  node: attr,
110
125
  messageId: 'keyOnlyOutermostElement',
126
+ data: {
127
+ elementName: getJSXElementName(openingElement.name),
128
+ },
111
129
  fix(fixer) {
112
130
  // Find the exact range of the attribute in the source code
113
131
  const startPos = attr.range[0];
@@ -0,0 +1,5 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ type MessageIds = 'moveGuardUp' | 'groupDerived' | 'moveDeclarationCloser' | 'moveSideEffect';
3
+ type Options = [];
4
+ export declare const logicalTopToBottomGrouping: TSESLint.RuleModule<MessageIds, Options>;
5
+ export {};