@mrpalmer/eslint-plugin 1.0.0

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.
@@ -0,0 +1,636 @@
1
+ import { TSESTree } from '@typescript-eslint/utils';
2
+ import { minimatch } from 'minimatch';
3
+ import { reverse, findOutOfOrder, groupBy } from '../utils/array.js';
4
+ import { findRootNode, findEndOfLineWithComments, findStartOfLineWithComments, } from '../utils/ast.js';
5
+ import { createRule } from '../utils/create-rule.js';
6
+ import { getSettings } from '../utils/settings.js';
7
+ import { loadConfig } from '../utils/ts-config.js';
8
+ import { importType, } from '../utils/import-type.js';
9
+ const categories = {
10
+ import: 'import',
11
+ exports: 'exports',
12
+ };
13
+ const defaultGroups = [
14
+ 'core',
15
+ 'builtin',
16
+ 'external',
17
+ 'relative',
18
+ 'sibling',
19
+ 'index',
20
+ ];
21
+ // REPORTING AND FIXING
22
+ function isRequireExpression(expr) {
23
+ return (expr != null &&
24
+ expr.type === TSESTree.AST_NODE_TYPES.CallExpression &&
25
+ // eslint-disable-next-line eslint-plugin/no-property-in-node
26
+ 'name' in expr.callee &&
27
+ expr.callee.name === 'require' &&
28
+ expr.arguments.length === 1 &&
29
+ expr.arguments[0].type === TSESTree.AST_NODE_TYPES.Literal);
30
+ }
31
+ function isSupportedRequireModule(node) {
32
+ if (node.type !== TSESTree.AST_NODE_TYPES.VariableDeclaration) {
33
+ return false;
34
+ }
35
+ if (node.declarations.length !== 1) {
36
+ return false;
37
+ }
38
+ const decl = node.declarations[0];
39
+ const isPlainRequire = (decl.id.type === TSESTree.AST_NODE_TYPES.Identifier ||
40
+ decl.id.type === TSESTree.AST_NODE_TYPES.ObjectPattern) &&
41
+ isRequireExpression(decl.init);
42
+ const isRequireWithMemberExpression = (decl.id.type === TSESTree.AST_NODE_TYPES.Identifier ||
43
+ decl.id.type === TSESTree.AST_NODE_TYPES.ObjectPattern) &&
44
+ decl.init != null &&
45
+ decl.init.type === TSESTree.AST_NODE_TYPES.CallExpression &&
46
+ decl.init.callee.type === TSESTree.AST_NODE_TYPES.MemberExpression &&
47
+ isRequireExpression(decl.init.callee.object);
48
+ return isPlainRequire || isRequireWithMemberExpression;
49
+ }
50
+ function isPlainImportModule(node) {
51
+ return node.type === TSESTree.AST_NODE_TYPES.ImportDeclaration;
52
+ }
53
+ function canCrossNodeWhileReorder(node) {
54
+ return isSupportedRequireModule(node) || isPlainImportModule(node);
55
+ }
56
+ function canReorderItems(firstNode, secondNode) {
57
+ const parent = firstNode.parent;
58
+ // eslint-disable-next-line eslint-plugin/no-property-in-node
59
+ if (!parent || !('body' in parent) || !Array.isArray(parent.body)) {
60
+ return false;
61
+ }
62
+ const body = parent.body;
63
+ const [firstIndex, secondIndex] = [
64
+ body.indexOf(firstNode),
65
+ body.indexOf(secondNode),
66
+ ].sort();
67
+ const nodesBetween = parent.body.slice(firstIndex, secondIndex + 1);
68
+ for (const nodeBetween of nodesBetween) {
69
+ if (!canCrossNodeWhileReorder(nodeBetween)) {
70
+ return false;
71
+ }
72
+ }
73
+ return true;
74
+ }
75
+ function makeImportDescription(node) {
76
+ if (node.type === 'export') {
77
+ if (node.node.exportKind === 'type') {
78
+ return 'type export';
79
+ }
80
+ return 'export';
81
+ }
82
+ if (node.node.importKind === 'type') {
83
+ return 'type import';
84
+ }
85
+ // @ts-expect-error - flow type
86
+ if (node.node.importKind === 'typeof') {
87
+ return 'typeof import';
88
+ }
89
+ return 'import';
90
+ }
91
+ function fixOutOfOrder(context, firstNode, secondNode, order, category) {
92
+ const isExports = category === categories.exports;
93
+ const { sourceCode } = context;
94
+ const firstRoot = findRootNode(firstNode.node);
95
+ const secondRoot = findRootNode(secondNode.node);
96
+ const firstRootStart = findStartOfLineWithComments(sourceCode, firstRoot);
97
+ const firstRootEnd = findEndOfLineWithComments(sourceCode, firstRoot);
98
+ const secondRootStart = findStartOfLineWithComments(sourceCode, secondRoot);
99
+ const secondRootEnd = findEndOfLineWithComments(sourceCode, secondRoot);
100
+ const firstDesc = makeImportDescription(firstNode);
101
+ const secondDesc = makeImportDescription(secondNode);
102
+ if (firstNode.displayName === secondNode.displayName &&
103
+ firstDesc === secondDesc) {
104
+ return;
105
+ }
106
+ const firstImport = `${firstDesc} of \`${firstNode.displayName}\``;
107
+ const secondImport = `\`${secondNode.displayName}\` ${secondDesc}`;
108
+ const messageOptions = {
109
+ messageId: 'order',
110
+ data: { firstImport, secondImport, order },
111
+ };
112
+ const canFix = isExports || canReorderItems(firstRoot, secondRoot);
113
+ let newCode = sourceCode.text.slice(secondRootStart, secondRootEnd);
114
+ if (!newCode.endsWith('\n')) {
115
+ newCode = `${newCode}\n`;
116
+ }
117
+ if (order === 'before') {
118
+ context.report({
119
+ node: secondNode.node,
120
+ ...messageOptions,
121
+ fix: canFix
122
+ ? (fixer) => fixer.replaceTextRange([firstRootStart, secondRootEnd], newCode + sourceCode.text.slice(firstRootStart, secondRootStart))
123
+ : null,
124
+ });
125
+ }
126
+ else {
127
+ context.report({
128
+ node: secondNode.node,
129
+ ...messageOptions,
130
+ fix: canFix
131
+ ? (fixer) => fixer.replaceTextRange([secondRootStart, firstRootEnd], sourceCode.text.slice(secondRootEnd, firstRootEnd) + newCode)
132
+ : null,
133
+ });
134
+ }
135
+ }
136
+ function reportOutOfOrder(context, imported, outOfOrder, order, category) {
137
+ for (const imp of outOfOrder) {
138
+ fixOutOfOrder(context,
139
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
140
+ imported.find((importedItem) => importedItem.rank > imp.rank), imp, order, category);
141
+ }
142
+ }
143
+ const compareString = (a, b) => {
144
+ if (a < b) {
145
+ return -1;
146
+ }
147
+ if (a > b) {
148
+ return 1;
149
+ }
150
+ return 0;
151
+ };
152
+ /** Some parsers (languages without types) don't provide ImportKind */
153
+ const DEFAULT_IMPORT_KIND = 'value';
154
+ const RELATIVE_DOTS = new Set(['.', '..']);
155
+ function getSorter() {
156
+ return function importsSorter(nodeA, nodeB) {
157
+ let result = 0;
158
+ if (!nodeA.sortableSource.includes('/') &&
159
+ !nodeB.sortableSource.includes('/')) {
160
+ result =
161
+ compareString(nodeA.sortableSource, nodeB.sortableSource) ||
162
+ compareString(nodeA.originalSource, nodeB.originalSource);
163
+ }
164
+ else {
165
+ const A = nodeA.sortableSource.split('/');
166
+ const OA = nodeA.originalSource.split('/');
167
+ const B = nodeB.sortableSource.split('/');
168
+ const OB = nodeB.originalSource.split('/');
169
+ const a = A.length;
170
+ const b = B.length;
171
+ for (let i = 0; i < Math.min(a, b); i++) {
172
+ // Skip comparing the first path segment, if they are relative segments for both imports
173
+ const x = A[i];
174
+ const y = B[i];
175
+ if (i === 0 && RELATIVE_DOTS.has(x) && RELATIVE_DOTS.has(y)) {
176
+ // If one is sibling and the other parent import, no need to compare at all, since the paths belong in different groups
177
+ if (x !== y) {
178
+ break;
179
+ }
180
+ continue;
181
+ }
182
+ result = compareString(x, y) || compareString(OA[i], OB[i]);
183
+ if (result) {
184
+ break;
185
+ }
186
+ }
187
+ if (!result && a !== b) {
188
+ result = a < b ? -1 : 1;
189
+ }
190
+ }
191
+ // In case the paths are equal (result === 0), sort them by importKind
192
+ result ||= compareString(nodeA.node.importKind ?? DEFAULT_IMPORT_KIND, nodeB.node.importKind ?? DEFAULT_IMPORT_KIND);
193
+ return result;
194
+ };
195
+ }
196
+ function mutateRanksToAlphabetize(imported) {
197
+ const groupedByRanks = groupBy(imported, (item) => item.rank);
198
+ const sorterFn = getSorter();
199
+ // sort group keys so that they can be iterated on in order
200
+ const groupRanks = Object.keys(groupedByRanks).sort((a, b) => +a - +b);
201
+ // sort imports locally within their group
202
+ for (const groupRank of groupRanks) {
203
+ if (groupRank === '0') {
204
+ // don't sort side-effect imports
205
+ continue;
206
+ }
207
+ groupedByRanks[groupRank].sort(sorterFn);
208
+ }
209
+ // assign globally unique rank to each import
210
+ let newRank = 0;
211
+ const alphabetizedRanks = groupRanks.reduce((acc, groupRank) => {
212
+ for (const importedItem of groupedByRanks[groupRank]) {
213
+ acc[`${importedItem.value}|${importedItem.node.importKind}`] =
214
+ Number.parseInt(groupRank, 10) + newRank;
215
+ newRank += 1;
216
+ }
217
+ return acc;
218
+ }, {});
219
+ // mutate the original group-rank with alphabetized-rank
220
+ for (const importedItem of imported) {
221
+ importedItem.rank =
222
+ alphabetizedRanks[`${importedItem.value}|${importedItem.node.importKind}`];
223
+ }
224
+ }
225
+ function makeOutOfOrderReport(context, imported, category) {
226
+ const outOfOrder = findOutOfOrder(imported);
227
+ if (outOfOrder.length === 0) {
228
+ return;
229
+ }
230
+ // There are things to report. Try to minimize the number of reported errors.
231
+ const reversedImported = reverse(imported);
232
+ const reversedOrder = findOutOfOrder(reversedImported);
233
+ if (reversedOrder.length < outOfOrder.length) {
234
+ reportOutOfOrder(context, reversedImported, reversedOrder, 'after', category);
235
+ return;
236
+ }
237
+ reportOutOfOrder(context, imported, outOfOrder, 'before', category);
238
+ }
239
+ // DETECTING
240
+ function computePathRank(ranks, pathGroups, path, maxPosition, isSideEffectOnly) {
241
+ for (const { pattern, patternOptions, group, position = 1, isSideEffect, } of pathGroups) {
242
+ if (minimatch(path, pattern, patternOptions ?? { nocomment: true }) &&
243
+ (isSideEffect == null || isSideEffect === isSideEffectOnly)) {
244
+ return ranks[group] + position / maxPosition;
245
+ }
246
+ }
247
+ return undefined;
248
+ }
249
+ function computeRank(settings, ranks, importEntry, isSideEffectOnly = false) {
250
+ let rank = typeof importEntry.value === 'string'
251
+ ? computePathRank(ranks.groups, ranks.pathGroups, importEntry.value, ranks.maxPosition, isSideEffectOnly)
252
+ : undefined;
253
+ if (rank === undefined) {
254
+ if (isSideEffectOnly) {
255
+ return ranks.groups.sideEffect;
256
+ }
257
+ const impType = importType(importEntry.value, settings);
258
+ rank = ranks.groups[impType];
259
+ if (rank === undefined &&
260
+ ranks.groups.core &&
261
+ impType.startsWith('core:')) {
262
+ rank = ranks.groups.core;
263
+ }
264
+ if (rank === undefined) {
265
+ return -1;
266
+ }
267
+ }
268
+ if (importEntry.type !== 'import') {
269
+ rank += 100;
270
+ }
271
+ return rank;
272
+ }
273
+ function registerNode(context, settings, importEntry, ranks, imported) {
274
+ const isSideEffectOnly = isSideEffectImport(importEntry.node, context.sourceCode);
275
+ const rank = computeRank(settings, ranks, importEntry, isSideEffectOnly);
276
+ if (rank !== -1) {
277
+ let importNode = importEntry.node;
278
+ if (importEntry.type === 'require' &&
279
+ importNode.parent?.parent?.type ===
280
+ TSESTree.AST_NODE_TYPES.VariableDeclaration) {
281
+ importNode = importNode.parent.parent;
282
+ }
283
+ imported.push({
284
+ ...importEntry,
285
+ rank,
286
+ index: imported.length,
287
+ sortableSource: transformSource(importEntry.value),
288
+ originalSource: String(importEntry.value),
289
+ isSideEffectOnly,
290
+ isMultiline: importNode.loc.end.line !== importNode.loc.start.line,
291
+ });
292
+ }
293
+ }
294
+ function isSideEffectImport(node, sourceCode) {
295
+ if (node.type !== TSESTree.AST_NODE_TYPES.ImportDeclaration) {
296
+ return false;
297
+ }
298
+ return (node.specifiers.length === 0 &&
299
+ node.importKind !== 'type' &&
300
+ !isPunctuator(sourceCode.getFirstToken(node, { skip: 1 }), '{'));
301
+ }
302
+ function isPunctuator(token, value) {
303
+ return (token &&
304
+ token.type === TSESTree.AST_TOKEN_TYPES.Punctuator &&
305
+ token.value === value);
306
+ }
307
+ function getRequireBlock(node) {
308
+ let n = node;
309
+ // Handle cases like `const baz = require('foo').bar.baz`
310
+ // and `const foo = require('foo')()`
311
+ while ((n.parent?.type === TSESTree.AST_NODE_TYPES.MemberExpression &&
312
+ n.parent.object === n) ||
313
+ (n.parent?.type === TSESTree.AST_NODE_TYPES.CallExpression &&
314
+ n.parent.callee === n)) {
315
+ n = n.parent;
316
+ }
317
+ if (n.parent?.type === TSESTree.AST_NODE_TYPES.VariableDeclarator &&
318
+ n.parent.parent.parent.type === TSESTree.AST_NODE_TYPES.Program) {
319
+ return n.parent.parent.parent;
320
+ }
321
+ return undefined;
322
+ }
323
+ const baseTypes = [
324
+ 'sideEffect',
325
+ 'core',
326
+ 'builtin',
327
+ 'external',
328
+ 'internal',
329
+ 'unknown',
330
+ 'relative',
331
+ 'sibling',
332
+ 'index',
333
+ ];
334
+ function transformSource(source) {
335
+ return (String(source)
336
+ .toLowerCase()
337
+ // Treat `.` as `./`, `..` as `../`, `../..` as `../../` etc.
338
+ .replace(/^[./]*\.$/, '$&/')
339
+ // Make `../` sort after `../../` but before `../a` etc.
340
+ // Why a comma? See the next comment.
341
+ .replace(/^[./]*\/$/, '$&,')
342
+ // Make `.` and `/` sort before any other punctuation.
343
+ // The default order is: _ - , x x x . x x x / x x x
344
+ // We’re changing it to: . / , x x x _ x x x - x x x
345
+ .replace(/[._-]/g, (char) => {
346
+ switch (char) {
347
+ case '.':
348
+ return '_';
349
+ case '_':
350
+ return '.';
351
+ case '-':
352
+ return '/';
353
+ default:
354
+ return char;
355
+ }
356
+ }));
357
+ }
358
+ // Creates an object with type-rank pairs.
359
+ // Example: { index: 0, sibling: 1, parent: 1, external: 1, builtin: 2, internal: 2 }
360
+ // Will throw an error if it contains a type that does not exist, or has a duplicate
361
+ function convertGroupsToRanks(groups, types, coreModules) {
362
+ const coreIndex = groups.indexOf('core');
363
+ if (coreIndex !== -1 && coreModules) {
364
+ groups = groups.toSpliced(coreIndex + 1, 0, ...coreModules.map((module) => `core:${module}`));
365
+ }
366
+ const rankObject = groups.reduce((res, group, index) => {
367
+ for (const groupItem of [group].flat()) {
368
+ if (!types.has(groupItem) && !groupItem.startsWith('core:')) {
369
+ throw new Error(`Incorrect configuration of the rule: Unknown type \`${JSON.stringify(groupItem)}\``);
370
+ }
371
+ if (res[groupItem] !== undefined) {
372
+ throw new Error(`Incorrect configuration of the rule: \`${groupItem}\` is duplicated`);
373
+ }
374
+ res[groupItem] = index * 2;
375
+ }
376
+ return res;
377
+ }, {});
378
+ const omittedTypes = new Set(types);
379
+ Object.keys(rankObject)
380
+ .filter((key) => types.has(key))
381
+ .forEach((key) => {
382
+ omittedTypes.delete(key);
383
+ });
384
+ if (coreIndex === -1) {
385
+ omittedTypes.add('core');
386
+ }
387
+ const ranks = [...omittedTypes].reduce((res, type) => {
388
+ res[type] = groups.length * 2;
389
+ return res;
390
+ }, rankObject);
391
+ assertAllTypesRanked(ranks);
392
+ return { groups: ranks, omittedTypes: [...omittedTypes] };
393
+ }
394
+ function assertAllTypesRanked(ranks) {
395
+ const expectedTypes = new Set(baseTypes);
396
+ expectedTypes.delete('core');
397
+ for (const type of expectedTypes) {
398
+ if (ranks[type] === undefined) {
399
+ throw new Error(`Incorrect configuration of the rule: \`${type}\` is missing`);
400
+ }
401
+ }
402
+ }
403
+ function convertPathGroupsForRanks(pathGroups, tsconfigPathAliases) {
404
+ const after = {};
405
+ const before = {};
406
+ const tsconfigPathGroups = tsconfigPathAliases.map((alias) => ({
407
+ pattern: alias.replace(/\/\*$/, '/**/*'),
408
+ group: 'internal',
409
+ }));
410
+ const transformed = [...pathGroups, ...tsconfigPathGroups].map((pathGroup, index) => {
411
+ const { group, position: positionString } = pathGroup;
412
+ let position = 0;
413
+ if (positionString === 'after') {
414
+ after[group] ||= 1;
415
+ position = after[group]++;
416
+ }
417
+ else if (positionString === 'before') {
418
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
419
+ before[group] ||= [];
420
+ before[group].push(index);
421
+ }
422
+ return { ...pathGroup, position };
423
+ });
424
+ let maxPosition = 1;
425
+ for (const group of Object.keys(before)) {
426
+ const groupLength = before[group].length;
427
+ for (const [index, groupIndex] of before[group].entries()) {
428
+ transformed[groupIndex].position = -1 * (groupLength - index);
429
+ }
430
+ maxPosition = Math.max(maxPosition, groupLength);
431
+ }
432
+ for (const key of Object.keys(after)) {
433
+ const groupNextPosition = after[key];
434
+ maxPosition = Math.max(maxPosition, groupNextPosition - 1);
435
+ }
436
+ return {
437
+ pathGroups: transformed,
438
+ maxPosition: maxPosition > 10 ? 10 ** Math.ceil(Math.log10(maxPosition)) : 10,
439
+ };
440
+ }
441
+ function removeNewLineAfterImport(context, currentImport, previousImport) {
442
+ const { sourceCode } = context;
443
+ const prevRoot = findRootNode(previousImport.node);
444
+ const currRoot = findRootNode(currentImport.node);
445
+ const rangeToRemove = [
446
+ findEndOfLineWithComments(sourceCode, prevRoot),
447
+ findStartOfLineWithComments(sourceCode, currRoot),
448
+ ];
449
+ if (/^\s*$/.test(sourceCode.text.slice(rangeToRemove[0], rangeToRemove[1]))) {
450
+ return (fixer) => fixer.removeRange(rangeToRemove);
451
+ }
452
+ return undefined;
453
+ }
454
+ function makeNewlinesBetweenReport(context, imported) {
455
+ const getNumberOfEmptyLinesBetween = (currentImport, previousImport) => {
456
+ return context.sourceCode.lines
457
+ .slice(previousImport.node.loc.end.line, currentImport.node.loc.start.line - 1)
458
+ .filter((line) => line.trim().length === 0).length;
459
+ };
460
+ let previousImport = imported[0];
461
+ for (const currentImport of imported.slice(1)) {
462
+ const emptyLinesBetween = getNumberOfEmptyLinesBetween(currentImport, previousImport);
463
+ if (emptyLinesBetween > 0) {
464
+ context.report({
465
+ node: previousImport.node,
466
+ messageId: 'noLineBetweenImports',
467
+ fix: removeNewLineAfterImport(context, currentImport, previousImport),
468
+ });
469
+ }
470
+ previousImport = currentImport;
471
+ }
472
+ }
473
+ export default createRule({
474
+ meta: {
475
+ type: 'suggestion',
476
+ docs: {
477
+ description: 'Ensure imports are sorted consistently.',
478
+ },
479
+ fixable: 'code',
480
+ schema: [
481
+ {
482
+ type: 'object',
483
+ properties: {
484
+ groups: {
485
+ type: 'array',
486
+ },
487
+ pathGroups: {
488
+ type: 'array',
489
+ items: {
490
+ type: 'object',
491
+ properties: {
492
+ pattern: {
493
+ type: 'string',
494
+ },
495
+ patternOptions: {
496
+ type: 'object',
497
+ },
498
+ group: {
499
+ type: 'string',
500
+ // enum: baseTypes,
501
+ },
502
+ position: {
503
+ type: 'string',
504
+ enum: ['after', 'before'],
505
+ },
506
+ isSideEffect: {
507
+ type: 'boolean',
508
+ },
509
+ },
510
+ additionalProperties: false,
511
+ required: ['pattern', 'group'],
512
+ },
513
+ },
514
+ },
515
+ additionalProperties: false,
516
+ },
517
+ ],
518
+ messages: {
519
+ error: '{{error}}',
520
+ noLineBetweenImports: 'There should be no empty line between import statements',
521
+ order: '{{secondImport}} should occur {{order}} {{firstImport}}',
522
+ },
523
+ },
524
+ defaultOptions: [
525
+ {
526
+ groups: defaultGroups,
527
+ pathGroups: [],
528
+ },
529
+ ],
530
+ create(context, [options]) {
531
+ const settings = getSettings(context);
532
+ const tsConfigPaths = loadConfig(settings)?.config.compilerOptions?.paths ?? {};
533
+ let ranks;
534
+ try {
535
+ const { pathGroups, maxPosition } = convertPathGroupsForRanks(options.pathGroups, Object.keys(tsConfigPaths));
536
+ const types = new Set(baseTypes);
537
+ pathGroups.forEach((group) => {
538
+ types.add(group.group);
539
+ });
540
+ const { groups, omittedTypes } = convertGroupsToRanks(['sideEffect', ...options.groups], types, settings['mrpalmer/coreModules']);
541
+ ranks = {
542
+ groups,
543
+ omittedTypes,
544
+ pathGroups,
545
+ maxPosition,
546
+ };
547
+ }
548
+ catch (error) {
549
+ // Malformed configuration
550
+ return {
551
+ Program(node) {
552
+ context.report({
553
+ node,
554
+ messageId: 'error',
555
+ data: {
556
+ error: getErrorMessage(error) ?? 'Invalid configuration',
557
+ },
558
+ });
559
+ },
560
+ };
561
+ }
562
+ const importMap = new Map();
563
+ const exportMap = new Map();
564
+ function getBlockImports(node) {
565
+ let blockImports = importMap.get(node);
566
+ if (!blockImports) {
567
+ importMap.set(node, (blockImports = []));
568
+ }
569
+ return blockImports;
570
+ }
571
+ return {
572
+ ImportDeclaration(node) {
573
+ const name = node.source.value;
574
+ registerNode(context, settings, {
575
+ node,
576
+ value: name,
577
+ displayName: name,
578
+ type: 'import',
579
+ }, ranks, getBlockImports(node.parent));
580
+ },
581
+ CallExpression(node) {
582
+ if (!isStaticRequire(node)) {
583
+ return;
584
+ }
585
+ const block = getRequireBlock(node);
586
+ const firstArg = node.arguments[0];
587
+ // eslint-disable-next-line eslint-plugin/no-property-in-node
588
+ if (!block || !('value' in firstArg)) {
589
+ return;
590
+ }
591
+ const { value } = firstArg;
592
+ registerNode(context, settings, {
593
+ node,
594
+ value,
595
+ displayName: value,
596
+ type: 'require',
597
+ }, ranks, getBlockImports(block));
598
+ },
599
+ 'Program:exit'() {
600
+ for (const imported of importMap.values()) {
601
+ makeNewlinesBetweenReport(context, imported);
602
+ mutateRanksToAlphabetize(imported);
603
+ makeOutOfOrderReport(context, imported, categories.import);
604
+ }
605
+ for (const exported of exportMap.values()) {
606
+ mutateRanksToAlphabetize(exported);
607
+ makeOutOfOrderReport(context, exported, categories.exports);
608
+ }
609
+ importMap.clear();
610
+ exportMap.clear();
611
+ },
612
+ };
613
+ },
614
+ });
615
+ function getErrorMessage(error) {
616
+ if (typeof error === 'string') {
617
+ return error;
618
+ }
619
+ if (error instanceof Error) {
620
+ return error.message;
621
+ }
622
+ if (typeof error === 'object' &&
623
+ error &&
624
+ 'message' in error &&
625
+ typeof error.message === 'string') {
626
+ return error.message;
627
+ }
628
+ return undefined;
629
+ }
630
+ function isStaticRequire(node) {
631
+ return (node.callee.type === TSESTree.AST_NODE_TYPES.Identifier &&
632
+ node.callee.name === 'require' &&
633
+ node.arguments.length === 1 &&
634
+ node.arguments[0].type === TSESTree.AST_NODE_TYPES.Literal &&
635
+ typeof node.arguments[0].value === 'string');
636
+ }
@@ -0,0 +1,8 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ export interface Options {
3
+ ignoreCase?: boolean;
4
+ types?: NamedTypes;
5
+ }
6
+ declare const _default: TSESLint.RuleModule<"order", [Options], unknown, TSESLint.RuleListener>;
7
+ export default _default;
8
+ type NamedTypes = 'mixed' | 'types-first' | 'types-last';