Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for using resolver object directly in settings #159

Merged
merged 7 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/red-rings-pay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-import-x": minor
---

feat: add support for using resolver object directly in settings
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,40 @@ module.exports = {
}
```

- use the `import` or `require` syntax to directly import the resolver object:

```js
// .eslintrc.mjs
import tsResolver from 'eslint-import-resolver-typescript'

export default {
settings: {
'import-x/resolver': {
name: 'tsResolver', // required, could be any string you like
// enable: false, // optional, defaults to true
options: { someConfig: value }, // optional, options to pass to the resolver
resolver: tsResolver, // required, the resolver object
},
},
}
```

```js
// .eslintrc.cjs
const tsResolver = require('eslint-import-resolver-typescript')

module.exports = {
settings: {
'import-x/resolver': {
name: 'tsResolver', // required, could be any string you like
// enable: false, // optional, defaults to true
options: { someConfig: value }, // optional, options to pass to the resolver
resolver: tsResolver, // required, the resolver object
},
},
}
```

Relative paths will be resolved relative to the source's nearest `package.json` or
the process's current working directory if no `package.json` is found.

Expand Down
75 changes: 67 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,73 @@ export type DocStyle = 'jsdoc' | 'tomdoc'

export type Arrayable<T> = T | readonly T[]

export type ResultNotFound = {
found: false
path?: undefined
}

export type ResultFound = {
found: true
path: string | null
}

export type ResolvedResult = ResultNotFound | ResultFound

export type ResolverResolve<T = unknown> = (
modulePath: string,
sourceFile: string,
config: T,
) => ResolvedResult

export type ResolverResolveImport<T = unknown> = (
modulePath: string,
sourceFile: string,
config: T,
) => string | undefined

export type Resolver<T = unknown, U = T> = {
interfaceVersion?: 1 | 2
resolve: ResolverResolve<T>
resolveImport: ResolverResolveImport<U>
}

export type ResolverName = LiteralUnion<
'node' | 'typescript' | 'webpack',
string
>

export type ResolverRecord = {
node?: boolean | NodeResolverOptions
typescript?: boolean | TsResolverOptions
webpack?: WebpackResolverOptions
[resolve: string]: unknown
}

export type ResolverObject = {
// node, typescript, webpack...
name: ResolverName

// Enabled by default
enable?: boolean

// Options passed to the resolver
options?:
| NodeResolverOptions
| TsResolverOptions
| WebpackResolverOptions
| unknown

// Any object satisfied Resolver type
resolver: Resolver
}

export type ImportResolver =
| LiteralUnion<'node' | 'typescript' | 'webpack', string>
| {
node?: boolean | NodeResolverOptions
typescript?: boolean | TsResolverOptions
webpack?: WebpackResolverOptions
[resolve: string]: unknown
}
| ResolverName
| ResolverRecord
| ResolverObject
| ResolverName[]
| ResolverRecord[]
| ResolverObject[]

export type ImportSettings = {
cache?: {
Expand All @@ -53,7 +112,7 @@ export type ImportSettings = {
internalRegex?: string
parsers?: Record<string, readonly FileExtension[]>
resolve?: NodeResolverOptions
resolver?: Arrayable<ImportResolver>
resolver?: ImportResolver
}

export type WithPluginName<T extends string | object> = T extends string
Expand Down
115 changes: 59 additions & 56 deletions src/utils/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,46 +5,18 @@
import stableHash from 'stable-hash'

import type {
Arrayable,
ImportResolver,
ImportSettings,
PluginSettings,
RuleContext,
Resolver,
ImportResolver,
ResolverRecord,
ResolverObject,
} from '../types'

import { ModuleCache } from './module-cache'
import { pkgDir } from './pkg-dir'

export type ResultNotFound = {
found: false
path?: undefined
}

export type ResultFound = {
found: true
path: string | null
}

export type ResolvedResult = ResultNotFound | ResultFound

export type ResolverResolve = (
modulePath: string,
sourceFile: string,
config: unknown,
) => ResolvedResult

export type ResolverResolveImport = (
modulePath: string,
sourceFile: string,
config: unknown,
) => string | undefined

export type Resolver = {
interfaceVersion?: 1 | 2
resolve: ResolverResolve
resolveImport: ResolverResolveImport
}

export const CASE_SENSITIVE_FS = !fs.existsSync(
path.resolve(
__dirname,
Expand Down Expand Up @@ -184,11 +156,14 @@
node: settings['import-x/resolve'],
} // backward compatibility

const resolvers = resolverReducer(configResolvers, new Map())
const resolvers = normalizeConfigResolvers(configResolvers, sourceFile)

for (const { enable, options, resolver } of resolvers) {
if (!enable) {
continue

Check warning on line 163 in src/utils/resolve.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/resolve.ts#L163

Added line #L163 was not covered by tests
}

for (const [name, config] of resolvers) {
const resolver = requireResolver(name, sourceFile)
const resolved = withResolver(resolver, config)
const resolved = withResolver(resolver, options)

if (!resolved.found) {
continue
Expand All @@ -212,30 +187,58 @@
return fullResolve(modulePath, sourceFile, settings).path
}

function resolverReducer(
resolvers: Arrayable<ImportResolver>,
map: Map<string, unknown>,
function normalizeConfigResolvers(
resolvers: ImportResolver,
sourceFile: string,
) {
if (Array.isArray(resolvers)) {
for (const r of resolvers as ImportResolver[]) resolverReducer(r, map)
return map
}

if (typeof resolvers === 'string') {
map.set(resolvers, null)
return map
}

if (typeof resolvers === 'object') {
for (const [key, value] of Object.entries(resolvers)) {
map.set(key, value)
const resolverArray = Array.isArray(resolvers) ? resolvers : [resolvers]
const map = new Map<string, Required<ResolverObject>>()

for (const nameOrRecordOrObject of resolverArray) {
if (typeof nameOrRecordOrObject === 'string') {
const name = nameOrRecordOrObject

map.set(name, {
name,
enable: true,
options: undefined,
resolver: requireResolver(name, sourceFile),
})
} else if (typeof nameOrRecordOrObject === 'object') {
if (nameOrRecordOrObject.name && nameOrRecordOrObject.resolver) {
const object = nameOrRecordOrObject as ResolverObject

const { name, enable = true, options, resolver } = object
map.set(name, { name, enable, options, resolver })
} else {
const record = nameOrRecordOrObject as ResolverRecord

for (const [name, enableOrOptions] of Object.entries(record)) {
if (typeof enableOrOptions === 'boolean') {
map.set(name, {
name,
enable: enableOrOptions,
options: undefined,
resolver: requireResolver(name, sourceFile),
})
} else {
map.set(name, {
name,
enable: true,
options: enableOrOptions,
resolver: requireResolver(name, sourceFile),
})
}
}
}
} else {
const err = new Error('invalid resolver config')
err.name = ERROR_NAME
throw err
}
return map
}

const err = new Error('invalid resolver config')
err.name = ERROR_NAME
throw err
return [...map.values()]
}

function getBaseDir(sourceFile: string): string {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

Loading
Loading