@blumintinc/eslint-plugin-blumint 1.20.176 → 1.20.177

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.176',
226
+ version: '1.20.177',
227
227
  },
228
228
  parseOptions: {
229
229
  ecmaVersion: 2020,
@@ -108,22 +108,62 @@ exports.jsdocAboveField = (0, createRule_1.createRule)({
108
108
  : node.range[1],
109
109
  };
110
110
  };
111
+ const containerOf = (node) => {
112
+ const parent = node.parent;
113
+ if (parent?.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
114
+ return { node: parent, members: parent.members, separator: ';' };
115
+ }
116
+ if (parent?.type === utils_1.AST_NODE_TYPES.TSInterfaceBody) {
117
+ return { node: parent, members: parent.body, separator: ';' };
118
+ }
119
+ if (parent?.type === utils_1.AST_NODE_TYPES.ClassBody) {
120
+ return { node: parent, members: parent.body, separator: ';' };
121
+ }
122
+ if (parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
123
+ return { node: parent, members: parent.properties, separator: ',' };
124
+ }
125
+ return undefined;
126
+ };
127
+ /**
128
+ * Whether nothing but the container's closing brace follows the block, so
129
+ * the field it trails is the only member it can belong to.
130
+ */
131
+ const closesContainer = (node, comment) => {
132
+ const container = containerOf(node);
133
+ if (!container) {
134
+ return false;
135
+ }
136
+ const closeBrace = sourceCode.getLastToken(container.node);
137
+ if (!closeBrace || comment.range[1] > closeBrace.range[0]) {
138
+ return false;
139
+ }
140
+ return !container.members.some((member) => member.range[0] >= comment.range[1]);
141
+ };
111
142
  /**
112
143
  * Attaches a trailing JSDoc block by token order rather than by line.
113
144
  *
114
- * Prettier reflows a multi-line block that trails a field onto its own
115
- * line, ahead of the member's separator: the block that followed
116
- * `timeout: number;` ends up between `timeout: number` and its `;`. Keying
117
- * on the comment sharing the field's line makes the rule inert on exactly
118
- * the formatted source it has to police, so a block the separator still
119
- * follows counts as this member's however many lines down it starts.
145
+ * Prettier is not idempotent on a multi-line block that trails a field. One
146
+ * pass reflows it onto its own line ahead of the member's separator, so the
147
+ * block that followed `timeout: number;` sits between `timeout: number` and
148
+ * its `;`; the next pass moves the separator back in front of it, and that
149
+ * separator-first spelling is the fixed point formatted source converges
150
+ * to. Both intermediates and the fixed point document the field above them,
151
+ * so keying on the comment sharing the field's line — or on the separator
152
+ * still following it — leaves the rule inert on the shape it exists to
153
+ * police.
154
+ *
155
+ * Past the separator the member has ended and position alone stops naming
156
+ * an owner: an own-line block there reads as the leading documentation of
157
+ * the next field. That reading needs a next field, so it is unavailable on
158
+ * the last member of a container, where the preceding field is the only
159
+ * candidate left.
120
160
  *
121
- * Past the separator the member has ended and position alone no longer
122
- * identifies an owner: an own-line block there is the leading documentation
123
- * of the next field, or a note about the enclosing shape. Only the same-line
124
- * spelling can be claimed there, which is why that arm survives.
161
+ * A blank line is the one signal that survives the round trip intact:
162
+ * prettier preserves an authored one and never inserts one while reflowing,
163
+ * so a block held off by an empty line is a deliberate note about the
164
+ * enclosing shape rather than displaced documentation.
125
165
  */
126
- const trailingJSDocFor = (span) => {
166
+ const trailingJSDocFor = (node, span) => {
127
167
  return allComments.find((comment) => {
128
168
  if (!isJSDocBlock(comment)) {
129
169
  return false;
@@ -131,11 +171,13 @@ exports.jsdocAboveField = (0, createRule_1.createRule)({
131
171
  if (comment.range[0] < span.offset) {
132
172
  return false;
133
173
  }
174
+ const between = sourceCode.text.slice(span.offset, comment.range[0]);
134
175
  const precedesSeparator = comment.range[1] <= span.separatorEnd;
135
176
  if (!precedesSeparator && comment.loc.start.line !== span.line) {
136
- return false;
177
+ if (!closesContainer(node, comment) || /\n[^\S\n]*\n/.test(between)) {
178
+ return false;
179
+ }
137
180
  }
138
- const between = sourceCode.text.slice(span.offset, comment.range[0]);
139
181
  return /^[\s;,]*$/.test(between);
140
182
  });
141
183
  };
@@ -236,22 +278,6 @@ exports.jsdocAboveField = (0, createRule_1.createRule)({
236
278
  */
237
279
  const lineIndentOf = (node) => /^[ \t]*/.exec(sourceCode.lines[node.loc.start.line - 1] ?? '')?.[0] ??
238
280
  '';
239
- const containerOf = (node) => {
240
- const parent = node.parent;
241
- if (parent?.type === utils_1.AST_NODE_TYPES.TSTypeLiteral) {
242
- return { node: parent, members: parent.members, separator: ';' };
243
- }
244
- if (parent?.type === utils_1.AST_NODE_TYPES.TSInterfaceBody) {
245
- return { node: parent, members: parent.body, separator: ';' };
246
- }
247
- if (parent?.type === utils_1.AST_NODE_TYPES.ClassBody) {
248
- return { node: parent, members: parent.body, separator: ';' };
249
- }
250
- if (parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
251
- return { node: parent, members: parent.properties, separator: ',' };
252
- }
253
- return undefined;
254
- };
255
281
  /**
256
282
  * A member's separator sits inside its range for type and class members
257
283
  * but outside it for object literal properties, so both spellings have to
@@ -314,7 +340,7 @@ exports.jsdocAboveField = (0, createRule_1.createRule)({
314
340
  jsdoc: member === node
315
341
  ? comment
316
342
  : isRelevantNode(member)
317
- ? trailingJSDocFor(fieldSpanOf(member))
343
+ ? trailingJSDocFor(member, fieldSpanOf(member))
318
344
  : undefined,
319
345
  };
320
346
  });
@@ -420,7 +446,7 @@ exports.jsdocAboveField = (0, createRule_1.createRule)({
420
446
  return;
421
447
  }
422
448
  const span = fieldSpanOf(node);
423
- const jsdocComment = trailingJSDocFor(span);
449
+ const jsdocComment = trailingJSDocFor(node, span);
424
450
  if (!jsdocComment) {
425
451
  return;
426
452
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blumintinc/eslint-plugin-blumint",
3
- "version": "1.20.176",
3
+ "version": "1.20.177",
4
4
  "description": "Custom eslint rules for use within BluMint",
5
5
  "author": {
6
6
  "name": "Brodie McGuire",
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "version": "1.20.177",
4
+ "date": "2026-08-26T16:36:01.459Z",
5
+ "rules": [
6
+ {
7
+ "name": "jsdoc-above-field",
8
+ "changeType": "fix",
9
+ "issues": [
10
+ 2145
11
+ ],
12
+ "summary": "claim a trailing block that closes its container (closes #2145)"
13
+ }
14
+ ]
15
+ },
2
16
  {
3
17
  "version": "1.20.176",
4
18
  "date": "2026-08-26T11:56:32.903Z",