Skip to content

Commit 3d7a551

Browse files
authored
refactor: migrate more rules (#48)
1 parent 6f53bfa commit 3d7a551

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1857
-1559
lines changed

.changeset/dull-spoons-fly.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
refactor: migrate more rules

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@
7676
"@types/debug": "^4.1.12",
7777
"@types/doctrine": "^0.0.9",
7878
"@types/eslint": "^8.56.5",
79+
"@types/is-glob": "^4.0.4",
7980
"@types/jest": "^29.5.12",
8081
"@types/json-schema": "^7.0.15",
8182
"@types/node": "^20.11.26",
+15-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
diff --git a/node_modules/@typescript-eslint/utils/dist/ts-eslint/Rule.d.ts b/node_modules/@typescript-eslint/utils/dist/ts-eslint/Rule.d.ts
2-
index 9a3a1fd..46b3961 100644
2+
index 9a3a1fd..6a2e2dd 100644
33
--- a/node_modules/@typescript-eslint/utils/dist/ts-eslint/Rule.d.ts
44
+++ b/node_modules/@typescript-eslint/utils/dist/ts-eslint/Rule.d.ts
5-
@@ -6,6 +6,10 @@ import type { Scope } from './Scope';
6-
import type { SourceCode } from './SourceCode';
5+
@@ -7,15 +7,13 @@ import type { SourceCode } from './SourceCode';
76
export type RuleRecommendation = 'error' | 'strict' | 'warn' | false;
87
interface RuleMetaDataDocs {
9-
+ /**
8+
/**
9+
- * Concise description of the rule
1010
+ * The category the rule falls under
11-
+ */
11+
*/
12+
- description: string;
1213
+ category?: string;
1314
/**
14-
* Concise description of the rule
15+
- * The recommendation level for the rule.
16+
- * Used by the build tools to generate the recommended and strict configs.
17+
- * Set to false to not include it as a recommendation
18+
+ * Concise description of the rule
19+
*/
20+
- recommended: 'error' | 'strict' | 'warn' | false;
21+
+ description: string;
22+
/**
23+
* The URL of the rule's docs
1524
*/

src/core/import-type.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function isModuleMain(name: string) {
8585
const scopedRegExp = /^@[^/]+\/?[^/]+/
8686

8787
export function isScoped(name: string) {
88-
return name && scopedRegExp.test(name)
88+
return !!name && scopedRegExp.test(name)
8989
}
9090

9191
const scopedMainRegExp = /^@[^/]+\/?[^/]+$/

src/export-map.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -814,8 +814,12 @@ export class ExportMap {
814814
}
815815
}
816816

817-
forEach(
818-
callback: (value: unknown, name: string, map: ExportMap) => void,
817+
forEach<T>(
818+
callback: (
819+
value: T | null | undefined,
820+
name: string,
821+
map: ExportMap,
822+
) => void,
819823
thisArg?: unknown,
820824
) {
821825
this.namespace.forEach((v, n) => {
@@ -835,7 +839,7 @@ export class ExportMap {
835839
return
836840
}
837841

838-
d.forEach((v, n) => {
842+
d.forEach<T>((v, n) => {
839843
if (n !== 'default') {
840844
callback.call(thisArg, v, n, this)
841845
}

src/import-declaration.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import type { Rule } from 'eslint'
1+
import { TSESTree } from '@typescript-eslint/utils'
22

3-
export const importDeclaration = (context: Rule.RuleContext) => {
3+
import { RuleContext } from './types'
4+
5+
export const importDeclaration = (context: RuleContext) => {
46
const ancestors = context.getAncestors()
5-
return ancestors[ancestors.length - 1]
7+
return ancestors[ancestors.length - 1] as TSESTree.ImportDeclaration
68
}

src/index.ts

+41-19
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,47 @@ import type { TSESLint } from '@typescript-eslint/utils'
22

33
import type { PluginConfig } from './types'
44

5+
// rules
56
import noUnresolved from './rules/no-unresolved'
67
import named from './rules/named'
78
import default_ from './rules/default'
9+
import namespace from './rules/namespace'
10+
import noNamespace from './rules/no-namespace'
11+
import export_ from './rules/export'
12+
import noMutableExports from './rules/no-mutable-exports'
13+
import extensions from './rules/extensions'
14+
import noRestrictedPaths from './rules/no-restricted-paths'
15+
import noInternalModules from './rules/no-internal-modules'
16+
import groupExports from './rules/group-exports'
17+
import noRelativePackages from './rules/no-relative-packages'
18+
import noRelativeParentImports from './rules/no-relative-parent-imports'
19+
import consistentTypeSpecifierStyle from './rules/consistent-type-specifier-style'
20+
21+
// configs
22+
import recommended from './config/recommended'
23+
import errors from './config/errors'
24+
import warnings from './config/warnings'
25+
import stage0 from './config/stage-0'
26+
import react from './config/react'
27+
import reactNative from './config/react-native'
28+
import electron from './config/electron'
29+
import typescript from './config/typescript'
830

931
export const rules = {
1032
'no-unresolved': noUnresolved,
1133
named,
1234
default: default_,
13-
namespace: require('./rules/namespace'),
14-
'no-namespace': require('./rules/no-namespace'),
15-
export: require('./rules/export'),
16-
'no-mutable-exports': require('./rules/no-mutable-exports'),
17-
extensions: require('./rules/extensions'),
18-
'no-restricted-paths': require('./rules/no-restricted-paths'),
19-
'no-internal-modules': require('./rules/no-internal-modules'),
20-
'group-exports': require('./rules/group-exports'),
21-
'no-relative-packages': require('./rules/no-relative-packages'),
22-
'no-relative-parent-imports': require('./rules/no-relative-parent-imports'),
23-
'consistent-type-specifier-style': require('./rules/consistent-type-specifier-style'),
35+
namespace,
36+
'no-namespace': noNamespace,
37+
export: export_,
38+
'no-mutable-exports': noMutableExports,
39+
extensions,
40+
'no-restricted-paths': noRestrictedPaths,
41+
'no-internal-modules': noInternalModules,
42+
'group-exports': groupExports,
43+
'no-relative-packages': noRelativePackages,
44+
'no-relative-parent-imports': noRelativeParentImports,
45+
'consistent-type-specifier-style': consistentTypeSpecifierStyle,
2446

2547
'no-self-import': require('./rules/no-self-import'),
2648
'no-cycle': require('./rules/no-cycle'),
@@ -63,17 +85,17 @@ export const rules = {
6385
} satisfies Record<string, TSESLint.RuleModule<string, readonly unknown[]>>
6486

6587
export const configs = {
66-
recommended: require('./config/recommended'),
88+
recommended,
6789

68-
errors: require('./config/errors'),
69-
warnings: require('./config/warnings'),
90+
errors,
91+
warnings,
7092

7193
// shhhh... work in progress "secret" rules
72-
'stage-0': require('./config/stage-0'),
94+
'stage-0': stage0,
7395

7496
// useful stuff for folks using various environments
75-
react: require('./config/react'),
76-
'react-native': require('./config/react-native'),
77-
electron: require('./config/electron'),
78-
typescript: require('./config/typescript'),
97+
react,
98+
'react-native': reactNative,
99+
electron,
100+
typescript,
79101
} satisfies Record<string, PluginConfig>

src/rules/consistent-type-specifier-style.js renamed to src/rules/consistent-type-specifier-style.ts

+59-28
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import { docsUrl } from '../docs-url'
1+
import { TSESLint, TSESTree } from '@typescript-eslint/utils'
2+
import { createRule } from '../utils'
23

3-
function isComma(token) {
4+
function isComma(token: TSESTree.Token): token is TSESTree.PunctuatorToken {
45
return token.type === 'Punctuator' && token.value === ','
56
}
67

7-
function removeSpecifiers(fixes, fixer, sourceCode, specifiers) {
8+
function removeSpecifiers(
9+
fixes: TSESLint.RuleFix[],
10+
fixer: TSESLint.RuleFixer,
11+
sourceCode: Readonly<TSESLint.SourceCode>,
12+
specifiers: TSESTree.ImportSpecifier[],
13+
) {
814
for (const specifier of specifiers) {
915
// remove the trailing comma
1016
const token = sourceCode.getTokenAfter(specifier)
@@ -15,7 +21,12 @@ function removeSpecifiers(fixes, fixer, sourceCode, specifiers) {
1521
}
1622
}
1723

18-
function getImportText(node, sourceCode, specifiers, kind) {
24+
function getImportText(
25+
node: TSESTree.ImportDeclaration,
26+
sourceCode: Readonly<TSESLint.SourceCode>,
27+
specifiers: TSESTree.ImportSpecifier[],
28+
kind: 'type' | 'typeof',
29+
) {
1930
const sourceString = sourceCode.getText(node.source)
2031
if (specifiers.length === 0) {
2132
return ''
@@ -31,14 +42,18 @@ function getImportText(node, sourceCode, specifiers, kind) {
3142
return `import ${kind} {${names.join(', ')}} from ${sourceString};`
3243
}
3344

34-
module.exports = {
45+
type Options = 'prefer-inline' | 'prefer-top-level'
46+
47+
type MessageId = 'inline' | 'topLevel'
48+
49+
export = createRule<[Options?], MessageId>({
50+
name: 'consistent-type-specifier-style',
3551
meta: {
3652
type: 'suggestion',
3753
docs: {
3854
category: 'Style guide',
3955
description:
4056
'Enforce or ban the use of inline type-only markers for named imports.',
41-
url: docsUrl('consistent-type-specifier-style'),
4257
},
4358
fixable: 'code',
4459
schema: [
@@ -48,8 +63,14 @@ module.exports = {
4863
default: 'prefer-inline',
4964
},
5065
],
66+
messages: {
67+
inline:
68+
'Prefer using inline {{kind}} specifiers instead of a top-level {{kind}}-only import.',
69+
topLevel:
70+
'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.',
71+
},
5172
},
52-
73+
defaultOptions: [],
5374
create(context) {
5475
const sourceCode = context.getSourceCode()
5576

@@ -75,20 +96,19 @@ module.exports = {
7596

7697
context.report({
7798
node,
78-
message:
79-
'Prefer using inline {{kind}} specifiers instead of a top-level {{kind}}-only import.',
99+
messageId: 'inline',
80100
data: {
81101
kind: node.importKind,
82102
},
83103
fix(fixer) {
84104
const kindToken = sourceCode.getFirstToken(node, { skip: 1 })
85105

86-
return [].concat(
106+
return [
87107
kindToken ? fixer.remove(kindToken) : [],
88108
node.specifiers.map(specifier =>
89109
fixer.insertTextBefore(specifier, `${node.importKind} `),
90110
),
91-
)
111+
].flat()
92112
},
93113
})
94114
},
@@ -101,6 +121,7 @@ module.exports = {
101121
if (
102122
// already top-level is valid
103123
node.importKind === 'type' ||
124+
// @ts-expect-error - flow type
104125
node.importKind === 'typeof' ||
105126
// no specifiers (import {} from '') cannot have inline - so is valid
106127
node.specifiers.length === 0 ||
@@ -113,19 +134,28 @@ module.exports = {
113134
return
114135
}
115136

116-
const typeSpecifiers = []
117-
const typeofSpecifiers = []
118-
const valueSpecifiers = []
119-
let defaultSpecifier = null
137+
const typeSpecifiers: TSESTree.ImportSpecifier[] = []
138+
const typeofSpecifiers: TSESTree.ImportSpecifier[] = []
139+
const valueSpecifiers: TSESTree.ImportSpecifier[] = []
140+
141+
let defaultSpecifier: TSESTree.ImportDefaultSpecifier | null = null
142+
120143
for (const specifier of node.specifiers) {
121144
if (specifier.type === 'ImportDefaultSpecifier') {
122145
defaultSpecifier = specifier
123146
continue
124147
}
125148

149+
if (!('importKind' in specifier)) {
150+
continue
151+
}
152+
126153
if (specifier.importKind === 'type') {
127154
typeSpecifiers.push(specifier)
128-
} else if (specifier.importKind === 'typeof') {
155+
} else if (
156+
// @ts-expect-error - flow type
157+
specifier.importKind === 'typeof'
158+
) {
129159
typeofSpecifiers.push(specifier)
130160
} else if (
131161
specifier.importKind === 'value' ||
@@ -154,15 +184,14 @@ module.exports = {
154184
node.specifiers.length
155185
) {
156186
// all specifiers have inline specifiers - so we replace the entire import
157-
const kind = [].concat(
158-
typeSpecifiers.length > 0 ? 'type' : [],
159-
typeofSpecifiers.length > 0 ? 'typeof' : [],
160-
)
187+
const kind = [
188+
typeSpecifiers.length > 0 ? ('type' as const) : [],
189+
typeofSpecifiers.length > 0 ? ('typeof' as const) : [],
190+
].flat()
161191

162192
context.report({
163193
node,
164-
message:
165-
'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.',
194+
messageId: 'topLevel',
166195
data: {
167196
kind: kind.join('/'),
168197
},
@@ -175,13 +204,12 @@ module.exports = {
175204
for (const specifier of typeSpecifiers.concat(typeofSpecifiers)) {
176205
context.report({
177206
node: specifier,
178-
message:
179-
'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.',
207+
messageId: 'topLevel',
180208
data: {
181209
kind: specifier.importKind,
182210
},
183211
fix(fixer) {
184-
const fixes = []
212+
const fixes: TSESLint.RuleFix[] = []
185213

186214
// if there are no value specifiers, then the other report fixer will be called, not this one
187215

@@ -201,7 +229,7 @@ module.exports = {
201229
// import { Value, } from 'mod';
202230
const maybeComma = sourceCode.getTokenAfter(
203231
valueSpecifiers[valueSpecifiers.length - 1],
204-
)
232+
)!
205233
if (isComma(maybeComma)) {
206234
fixes.push(fixer.remove(maybeComma))
207235
}
@@ -220,7 +248,10 @@ module.exports = {
220248
token => token.type === 'Punctuator' && token.value === '}',
221249
)
222250
fixes.push(
223-
fixer.removeRange([comma.range[0], closingBrace.range[1]]),
251+
fixer.removeRange([
252+
comma!.range[0],
253+
closingBrace!.range[1],
254+
]),
224255
)
225256
}
226257

@@ -235,4 +266,4 @@ module.exports = {
235266
},
236267
}
237268
},
238-
}
269+
})

src/rules/default.ts

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export = createRule<[], MessageId>({
1212
category: 'Static analysis',
1313
description:
1414
'Ensure a default export is present, given a default import.',
15-
recommended: 'warn',
1615
},
1716
schema: [],
1817
messages: {

0 commit comments

Comments
 (0)