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 = /(?:\r\n|\n|\r)([^\S\r\n]*)\S/.exec(interior)?.[1] ?? ' ';
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.12",
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",