eslint-plugin-imports-regulation 0.2.8 → 0.2.10

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.
@@ -1,5 +1,5 @@
1
1
  import type { TSESLint } from '@typescript-eslint/utils';
2
- type MessageIds = 'groupOrder' | 'blankLinesBetween' | 'unexpectedBlankLine' | 'typeSpecifiersLast' | 'markTypeOnly' | 'typeOnlyImport' | 'typeOnlySpecifier' | 'blankLinesAfter';
2
+ type MessageIds = 'groupOrder' | 'blankLinesBetween' | 'unexpectedBlankLine' | 'blankLineBelowComment' | 'typeSpecifiersLast' | 'markTypeOnly' | 'typeOnlyImport' | 'typeOnlySpecifier' | 'blankLinesAfter';
3
3
  type Origin = 'external' | 'internal';
4
4
  type Options = [
5
5
  {
@@ -66,6 +66,7 @@ const rule = {
66
66
  groupOrder: 'Expected this {{group}} import before the {{other}} imports.',
67
67
  blankLinesBetween: 'Expected {{expected}} blank {{lines}} between external imports and internal imports, found {{actual}}.',
68
68
  unexpectedBlankLine: 'Expected no blank lines between imports in the same group, found {{actual}}.',
69
+ blankLineBelowComment: 'Expected no blank lines between a comment and the import it belongs to, found {{actual}}.',
69
70
  typeSpecifiersLast: 'Type specifiers must come after value specifiers.',
70
71
  markTypeOnly: 'An import of nothing but types must be written `import type { A }`.',
71
72
  typeOnlyImport: 'Everything this imports is only used as a type — write `import type`.',
@@ -242,7 +243,9 @@ const rule = {
242
243
  for (const variable of source.getDeclaredVariables(node)) {
243
244
  if (variable.references.length === 0)
244
245
  continue;
245
- if (variable.references.every(reference => reference.isTypeReference))
246
+ // A plain `export { x }` is flagged as both, and could be a value.
247
+ const typeOnly = variable.references.every(reference => reference.isTypeReference && !reference.isValueReference);
248
+ if (typeOnly)
246
249
  names.add(variable.name);
247
250
  }
248
251
  return names;
@@ -389,6 +392,32 @@ const rule = {
389
392
  }
390
393
  return entries;
391
394
  };
395
+ /**
396
+ * An entry's text with the blank lines below its comments taken out, and how many there were.
397
+ * Only the whitespace between comments is looked at: a blank line inside a block comment is
398
+ * the comment's own.
399
+ */
400
+ const tightened = (entry) => {
401
+ let blank = 0;
402
+ let text = '';
403
+ let cursor = entry.start;
404
+ const closeGap = (to) => {
405
+ const gap = source.text.slice(cursor, to);
406
+ const breaks = (gap.match(BREAK_GLOBAL) ?? []).length;
407
+ blank += Math.max(breaks - 1, 0);
408
+ text += breaks > 1 ? `${eol}${/[^\r\n]*$/.exec(gap)?.[0] ?? ''}` : gap;
409
+ };
410
+ for (const comment of source.getAllComments()) {
411
+ if (comment.range[0] < entry.start || comment.range[1] > entry.node.range[0])
412
+ continue;
413
+ closeGap(comment.range[0]);
414
+ text += source.text.slice(comment.range[0], comment.range[1]);
415
+ cursor = comment.range[1];
416
+ }
417
+ closeGap(entry.node.range[0]);
418
+ text += source.text.slice(entry.node.range[0], entry.end);
419
+ return { blank, text };
420
+ };
392
421
  const lineAt = (index) => source.lines[index] ?? '';
393
422
  const isBlank = (index) => lineAt(index).trim() === '';
394
423
  /**
@@ -438,13 +467,15 @@ const rule = {
438
467
  fix: fixer => fixer.replaceTextRange([from, to], eol.repeat(blankLinesAfter + 1)),
439
468
  });
440
469
  };
441
- const checkChunk = (chunk) => {
470
+ const checkChunk = (chunk, leading) => {
442
471
  const entries = entriesOf(chunk);
443
472
  const first = entries[0];
444
473
  const last = entries[entries.length - 1];
445
474
  if (!first || !last)
446
475
  return;
447
- checkAfterImports(last);
476
+ // An import after code is not the imports block, so no gap is owed after it.
477
+ if (leading)
478
+ checkAfterImports(last);
448
479
  if (entries.length < 2)
449
480
  return;
450
481
  const rewrite = fixer => {
@@ -457,7 +488,7 @@ const rule = {
457
488
  const boundary = isInternalGroup(entry.group) !== isInternalGroup(previous.group);
458
489
  text += eol.repeat(boundary ? blankLinesBetween + 1 : 1);
459
490
  }
460
- text += source.text.slice(entry.start, entry.end);
491
+ text += tightened(entry).text;
461
492
  previous = entry;
462
493
  }
463
494
  return fixer.replaceTextRange([first.start, last.end], text);
@@ -484,6 +515,15 @@ const rule = {
484
515
  const blank = Math.max((gap.match(BREAK_GLOBAL) ?? []).length - 1, 0);
485
516
  const boundary = isInternalGroup(entry.group) !== isInternalGroup(previous.group);
486
517
  const wanted = boundary ? blankLinesBetween : 0;
518
+ const below = tightened(entry).blank;
519
+ if (below > 0) {
520
+ context.report({
521
+ node: entry.node,
522
+ messageId: 'blankLineBelowComment',
523
+ data: { actual: String(below) },
524
+ fix: rewrite,
525
+ });
526
+ }
487
527
  if (blank === wanted)
488
528
  continue;
489
529
  // Every gap, each with the whole-run fix: ESLint applies one, and the rest are gone on
@@ -512,7 +552,7 @@ const rule = {
512
552
  let chunk = [];
513
553
  const flush = () => {
514
554
  if (chunk.length > 0)
515
- checkChunk(chunk);
555
+ checkChunk(chunk, chunk[0] === program.body[0]);
516
556
  chunk = [];
517
557
  };
518
558
  for (const statement of program.body) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-imports-regulation",
3
- "version": "0.2.8",
3
+ "version": "0.2.10",
4
4
  "description": "Order imports: external type-only, external, blank line, internal type-only, internal — with type specifiers last inside each import.",
5
5
  "author": "Robert Sandiford",
6
6
  "type": "module",