Skip to content

Commit

Permalink
refactor: migrate more rules (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
JounQin committed Mar 14, 2024
1 parent 6f53bfa commit 3d7a551
Show file tree
Hide file tree
Showing 42 changed files with 1,857 additions and 1,559 deletions.
5 changes: 5 additions & 0 deletions .changeset/dull-spoons-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-import-x": patch
---

refactor: migrate more rules
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"@types/debug": "^4.1.12",
"@types/doctrine": "^0.0.9",
"@types/eslint": "^8.56.5",
"@types/is-glob": "^4.0.4",
"@types/jest": "^29.5.12",
"@types/json-schema": "^7.0.15",
"@types/node": "^20.11.26",
Expand Down
21 changes: 15 additions & 6 deletions patches/@typescript-eslint+utils+5.62.0.patch
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
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
index 9a3a1fd..46b3961 100644
index 9a3a1fd..6a2e2dd 100644
--- a/node_modules/@typescript-eslint/utils/dist/ts-eslint/Rule.d.ts
+++ b/node_modules/@typescript-eslint/utils/dist/ts-eslint/Rule.d.ts
@@ -6,6 +6,10 @@ import type { Scope } from './Scope';
import type { SourceCode } from './SourceCode';
@@ -7,15 +7,13 @@ import type { SourceCode } from './SourceCode';
export type RuleRecommendation = 'error' | 'strict' | 'warn' | false;
interface RuleMetaDataDocs {
+ /**
/**
- * Concise description of the rule
+ * The category the rule falls under
+ */
*/
- description: string;
+ category?: string;
/**
* Concise description of the rule
- * The recommendation level for the rule.
- * Used by the build tools to generate the recommended and strict configs.
- * Set to false to not include it as a recommendation
+ * Concise description of the rule
*/
- recommended: 'error' | 'strict' | 'warn' | false;
+ description: string;
/**
* The URL of the rule's docs
*/
2 changes: 1 addition & 1 deletion src/core/import-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function isModuleMain(name: string) {
const scopedRegExp = /^@[^/]+\/?[^/]+/

export function isScoped(name: string) {
return name && scopedRegExp.test(name)
return !!name && scopedRegExp.test(name)
}

const scopedMainRegExp = /^@[^/]+\/?[^/]+$/
Expand Down
10 changes: 7 additions & 3 deletions src/export-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -814,8 +814,12 @@ export class ExportMap {
}
}

forEach(
callback: (value: unknown, name: string, map: ExportMap) => void,
forEach<T>(
callback: (
value: T | null | undefined,
name: string,
map: ExportMap,
) => void,
thisArg?: unknown,
) {
this.namespace.forEach((v, n) => {
Expand All @@ -835,7 +839,7 @@ export class ExportMap {
return
}

d.forEach((v, n) => {
d.forEach<T>((v, n) => {
if (n !== 'default') {
callback.call(thisArg, v, n, this)
}
Expand Down
8 changes: 5 additions & 3 deletions src/import-declaration.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Rule } from 'eslint'
import { TSESTree } from '@typescript-eslint/utils'

export const importDeclaration = (context: Rule.RuleContext) => {
import { RuleContext } from './types'

export const importDeclaration = (context: RuleContext) => {
const ancestors = context.getAncestors()
return ancestors[ancestors.length - 1]
return ancestors[ancestors.length - 1] as TSESTree.ImportDeclaration
}
60 changes: 41 additions & 19 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,47 @@ import type { TSESLint } from '@typescript-eslint/utils'

import type { PluginConfig } from './types'

// rules
import noUnresolved from './rules/no-unresolved'
import named from './rules/named'
import default_ from './rules/default'
import namespace from './rules/namespace'
import noNamespace from './rules/no-namespace'
import export_ from './rules/export'
import noMutableExports from './rules/no-mutable-exports'
import extensions from './rules/extensions'
import noRestrictedPaths from './rules/no-restricted-paths'
import noInternalModules from './rules/no-internal-modules'
import groupExports from './rules/group-exports'
import noRelativePackages from './rules/no-relative-packages'
import noRelativeParentImports from './rules/no-relative-parent-imports'
import consistentTypeSpecifierStyle from './rules/consistent-type-specifier-style'

// configs
import recommended from './config/recommended'
import errors from './config/errors'
import warnings from './config/warnings'
import stage0 from './config/stage-0'
import react from './config/react'
import reactNative from './config/react-native'
import electron from './config/electron'
import typescript from './config/typescript'

export const rules = {
'no-unresolved': noUnresolved,
named,
default: default_,
namespace: require('./rules/namespace'),
'no-namespace': require('./rules/no-namespace'),
export: require('./rules/export'),
'no-mutable-exports': require('./rules/no-mutable-exports'),
extensions: require('./rules/extensions'),
'no-restricted-paths': require('./rules/no-restricted-paths'),
'no-internal-modules': require('./rules/no-internal-modules'),
'group-exports': require('./rules/group-exports'),
'no-relative-packages': require('./rules/no-relative-packages'),
'no-relative-parent-imports': require('./rules/no-relative-parent-imports'),
'consistent-type-specifier-style': require('./rules/consistent-type-specifier-style'),
namespace,
'no-namespace': noNamespace,
export: export_,
'no-mutable-exports': noMutableExports,
extensions,
'no-restricted-paths': noRestrictedPaths,
'no-internal-modules': noInternalModules,
'group-exports': groupExports,
'no-relative-packages': noRelativePackages,
'no-relative-parent-imports': noRelativeParentImports,
'consistent-type-specifier-style': consistentTypeSpecifierStyle,

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

export const configs = {
recommended: require('./config/recommended'),
recommended,

errors: require('./config/errors'),
warnings: require('./config/warnings'),
errors,
warnings,

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

// useful stuff for folks using various environments
react: require('./config/react'),
'react-native': require('./config/react-native'),
electron: require('./config/electron'),
typescript: require('./config/typescript'),
react,
'react-native': reactNative,
electron,
typescript,
} satisfies Record<string, PluginConfig>
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { docsUrl } from '../docs-url'
import { TSESLint, TSESTree } from '@typescript-eslint/utils'
import { createRule } from '../utils'

function isComma(token) {
function isComma(token: TSESTree.Token): token is TSESTree.PunctuatorToken {
return token.type === 'Punctuator' && token.value === ','
}

function removeSpecifiers(fixes, fixer, sourceCode, specifiers) {
function removeSpecifiers(
fixes: TSESLint.RuleFix[],
fixer: TSESLint.RuleFixer,
sourceCode: Readonly<TSESLint.SourceCode>,
specifiers: TSESTree.ImportSpecifier[],
) {
for (const specifier of specifiers) {
// remove the trailing comma
const token = sourceCode.getTokenAfter(specifier)
Expand All @@ -15,7 +21,12 @@ function removeSpecifiers(fixes, fixer, sourceCode, specifiers) {
}
}

function getImportText(node, sourceCode, specifiers, kind) {
function getImportText(
node: TSESTree.ImportDeclaration,
sourceCode: Readonly<TSESLint.SourceCode>,
specifiers: TSESTree.ImportSpecifier[],
kind: 'type' | 'typeof',
) {
const sourceString = sourceCode.getText(node.source)
if (specifiers.length === 0) {
return ''
Expand All @@ -31,14 +42,18 @@ function getImportText(node, sourceCode, specifiers, kind) {
return `import ${kind} {${names.join(', ')}} from ${sourceString};`
}

module.exports = {
type Options = 'prefer-inline' | 'prefer-top-level'

type MessageId = 'inline' | 'topLevel'

export = createRule<[Options?], MessageId>({
name: 'consistent-type-specifier-style',
meta: {
type: 'suggestion',
docs: {
category: 'Style guide',
description:
'Enforce or ban the use of inline type-only markers for named imports.',
url: docsUrl('consistent-type-specifier-style'),
},
fixable: 'code',
schema: [
Expand All @@ -48,8 +63,14 @@ module.exports = {
default: 'prefer-inline',
},
],
messages: {
inline:
'Prefer using inline {{kind}} specifiers instead of a top-level {{kind}}-only import.',
topLevel:
'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.',
},
},

defaultOptions: [],
create(context) {
const sourceCode = context.getSourceCode()

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

context.report({
node,
message:
'Prefer using inline {{kind}} specifiers instead of a top-level {{kind}}-only import.',
messageId: 'inline',
data: {
kind: node.importKind,
},
fix(fixer) {
const kindToken = sourceCode.getFirstToken(node, { skip: 1 })

return [].concat(
return [
kindToken ? fixer.remove(kindToken) : [],
node.specifiers.map(specifier =>
fixer.insertTextBefore(specifier, `${node.importKind} `),
),
)
].flat()
},
})
},
Expand All @@ -101,6 +121,7 @@ module.exports = {
if (
// already top-level is valid
node.importKind === 'type' ||
// @ts-expect-error - flow type
node.importKind === 'typeof' ||
// no specifiers (import {} from '') cannot have inline - so is valid
node.specifiers.length === 0 ||
Expand All @@ -113,19 +134,28 @@ module.exports = {
return
}

const typeSpecifiers = []
const typeofSpecifiers = []
const valueSpecifiers = []
let defaultSpecifier = null
const typeSpecifiers: TSESTree.ImportSpecifier[] = []
const typeofSpecifiers: TSESTree.ImportSpecifier[] = []
const valueSpecifiers: TSESTree.ImportSpecifier[] = []

let defaultSpecifier: TSESTree.ImportDefaultSpecifier | null = null

for (const specifier of node.specifiers) {
if (specifier.type === 'ImportDefaultSpecifier') {
defaultSpecifier = specifier
continue
}

if (!('importKind' in specifier)) {
continue
}

if (specifier.importKind === 'type') {
typeSpecifiers.push(specifier)
} else if (specifier.importKind === 'typeof') {
} else if (
// @ts-expect-error - flow type
specifier.importKind === 'typeof'
) {
typeofSpecifiers.push(specifier)
} else if (
specifier.importKind === 'value' ||
Expand Down Expand Up @@ -154,15 +184,14 @@ module.exports = {
node.specifiers.length
) {
// all specifiers have inline specifiers - so we replace the entire import
const kind = [].concat(
typeSpecifiers.length > 0 ? 'type' : [],
typeofSpecifiers.length > 0 ? 'typeof' : [],
)
const kind = [
typeSpecifiers.length > 0 ? ('type' as const) : [],
typeofSpecifiers.length > 0 ? ('typeof' as const) : [],
].flat()

context.report({
node,
message:
'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.',
messageId: 'topLevel',
data: {
kind: kind.join('/'),
},
Expand All @@ -175,13 +204,12 @@ module.exports = {
for (const specifier of typeSpecifiers.concat(typeofSpecifiers)) {
context.report({
node: specifier,
message:
'Prefer using a top-level {{kind}}-only import instead of inline {{kind}} specifiers.',
messageId: 'topLevel',
data: {
kind: specifier.importKind,
},
fix(fixer) {
const fixes = []
const fixes: TSESLint.RuleFix[] = []

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

Expand All @@ -201,7 +229,7 @@ module.exports = {
// import { Value, } from 'mod';
const maybeComma = sourceCode.getTokenAfter(
valueSpecifiers[valueSpecifiers.length - 1],
)
)!
if (isComma(maybeComma)) {
fixes.push(fixer.remove(maybeComma))
}
Expand All @@ -220,7 +248,10 @@ module.exports = {
token => token.type === 'Punctuator' && token.value === '}',
)
fixes.push(
fixer.removeRange([comma.range[0], closingBrace.range[1]]),
fixer.removeRange([
comma!.range[0],
closingBrace!.range[1],
]),
)
}

Expand All @@ -235,4 +266,4 @@ module.exports = {
},
}
},
}
})
1 change: 0 additions & 1 deletion src/rules/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export = createRule<[], MessageId>({
category: 'Static analysis',
description:
'Ensure a default export is present, given a default import.',
recommended: 'warn',
},
schema: [],
messages: {
Expand Down
Loading

0 comments on commit 3d7a551

Please sign in to comment.