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
|
-
|
|
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
|
-
|
|
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 +=
|
|
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.
|
|
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",
|