eslint-plugin-imports-regulation 0.2.12 → 0.2.13
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' | 'exportBlock' | 'blankLinesBetween' | 'unexpectedBlankLine' | 'blankLineBelowComment' | 'typeSpecifiersLast' | 'markTypeOnly' | 'typeOnlyImport' | 'typeOnlySpecifier' | 'blankLinesAfter' | 'multilineBraces';
|
|
2
|
+
type MessageIds = 'groupOrder' | 'exportBlock' | 'blankLinesBetween' | 'unexpectedBlankLine' | 'blankLineBelowComment' | 'typeSpecifiersLast' | 'typeSpecifiersFirst' | 'markTypeOnly' | 'typeOnlyImport' | 'typeOnlySpecifier' | 'blankLinesAfter' | 'multilineBraces';
|
|
3
3
|
type Origin = 'external' | 'internal';
|
|
4
4
|
type Options = [
|
|
5
5
|
{
|
|
@@ -69,6 +69,7 @@ const rule = {
|
|
|
69
69
|
unexpectedBlankLine: 'Expected no blank lines between imports in the same group, found {{actual}}.',
|
|
70
70
|
blankLineBelowComment: 'Expected no blank lines between a comment and the import it belongs to, found {{actual}}.',
|
|
71
71
|
typeSpecifiersLast: 'Type specifiers must come after value specifiers.',
|
|
72
|
+
typeSpecifiersFirst: 'Across several lines, type specifiers must come first, on lines of their own.',
|
|
72
73
|
markTypeOnly: 'An import of nothing but types must be written `import type { A }`.',
|
|
73
74
|
typeOnlyImport: 'Everything this imports is only used as a type — write `import type`.',
|
|
74
75
|
typeOnlySpecifier: '`{{name}}` is only used as a type — mark it `type`.',
|
|
@@ -191,6 +192,15 @@ const rule = {
|
|
|
191
192
|
}
|
|
192
193
|
return { carried, tail };
|
|
193
194
|
};
|
|
195
|
+
const shapeOf = (range, named) => {
|
|
196
|
+
const interior = source.text.slice(range[0], range[1]);
|
|
197
|
+
const indent = /(?:\r\n|\n|\r)([^\S\r\n]*)\S/.exec(interior)?.[1] ?? ' ';
|
|
198
|
+
const closeIndent = /(?:\r\n|\n|\r)([^\S\r\n]*)$/.exec(interior)?.[1] ?? '';
|
|
199
|
+
// From the tokens, not the text: a comment after the last comma hides it from a regex.
|
|
200
|
+
const last = named[named.length - 1];
|
|
201
|
+
const trailingComma = last !== undefined && source.getTokenAfter(last)?.value === ',';
|
|
202
|
+
return { indent, closeIndent, trailingComma };
|
|
203
|
+
};
|
|
194
204
|
/** Re-join specifiers in the shape the braces already had, carrying their comments with them. */
|
|
195
205
|
const rejoin = (range, named, ordered, textOf) => {
|
|
196
206
|
const interior = source.text.slice(range[0], range[1]);
|
|
@@ -205,11 +215,7 @@ const rule = {
|
|
|
205
215
|
});
|
|
206
216
|
return `${pad}${parts.join(', ')}${tail.map(comment => ` ${comment}`).join('')}${pad}`;
|
|
207
217
|
}
|
|
208
|
-
const indent =
|
|
209
|
-
const closeIndent = /(?:\r\n|\n|\r)([^\S\r\n]*)$/.exec(interior)?.[1] ?? '';
|
|
210
|
-
// From the tokens, not the text: a comment after the last comma hides it from a regex.
|
|
211
|
-
const last = named[named.length - 1];
|
|
212
|
-
const trailingComma = last !== undefined && source.getTokenAfter(last)?.value === ',';
|
|
218
|
+
const { indent, closeIndent, trailingComma } = shapeOf(range, named);
|
|
213
219
|
const lines = [];
|
|
214
220
|
for (const [index, specifier] of ordered.entries()) {
|
|
215
221
|
const { leading, beforeComma, afterComma } = pieceOf(specifier);
|
|
@@ -329,6 +335,30 @@ const rule = {
|
|
|
329
335
|
}
|
|
330
336
|
if (types.length === 0 || values.length === 0)
|
|
331
337
|
return;
|
|
338
|
+
const first = named[0];
|
|
339
|
+
const last = named[named.length - 1];
|
|
340
|
+
if (first && last && first.loc.start.line !== last.loc.end.line) {
|
|
341
|
+
const firstValueLine = Math.min(...values.map(specifier => specifier.loc.start.line));
|
|
342
|
+
const late = types.find(specifier => specifier.loc.end.line >= firstValueLine);
|
|
343
|
+
if (!late)
|
|
344
|
+
return;
|
|
345
|
+
const packed = named.some((specifier, i) => named[i - 1]?.loc.end.line === specifier.loc.start.line);
|
|
346
|
+
const commented = source.getAllComments().some(c => c.range[0] >= range[0] && c.range[1] <= range[1]);
|
|
347
|
+
context.report({
|
|
348
|
+
node: late,
|
|
349
|
+
messageId: 'typeSpecifiersFirst',
|
|
350
|
+
fix: fixer => {
|
|
351
|
+
if (!packed || commented) {
|
|
352
|
+
return fixer.replaceTextRange(range, rejoin(range, named, [...types, ...values], specifier => source.getText(specifier)));
|
|
353
|
+
}
|
|
354
|
+
const { indent, closeIndent, trailingComma } = shapeOf(range, named);
|
|
355
|
+
const line = (group) => `${indent}${group.map(specifier => source.getText(specifier)).join(', ')}`;
|
|
356
|
+
const text = `${line(types)},${eol}${line(values)}${trailingComma ? ',' : ''}`;
|
|
357
|
+
return fixer.replaceTextRange(range, `${eol}${text}${eol}${closeIndent}`);
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
332
362
|
const firstType = named.findIndex(specifier => specifier.importKind === 'type');
|
|
333
363
|
const misplaced = named.slice(firstType).find(specifier => specifier.importKind !== 'type');
|
|
334
364
|
if (!misplaced)
|
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.13",
|
|
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",
|