Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: import-js/eslint-import-resolver-typescript
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.6.0
Choose a base ref
...
head repository: import-js/eslint-import-resolver-typescript
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.7.0
Choose a head ref
  • 2 commits
  • 14 files changed
  • 1 contributor

Commits on Mar 23, 2022

  1. Copy the full SHA
    1e39028 View commit details
  2. chore(release): 2.7.0

    JounQin committed Mar 23, 2022
    Copy the full SHA
    9ec18d7 View commit details
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,13 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [2.7.0](https://github.com/alexgorbatchev/eslint-import-resolver-typescript/compare/v2.6.0...v2.7.0) (2022-03-23)


### Features

* support `.cjs` `.mjs` `.cts` `.mts` extensions ([#84](https://github.com/alexgorbatchev/eslint-import-resolver-typescript/issues/84)) ([1e39028](https://github.com/alexgorbatchev/eslint-import-resolver-typescript/commit/1e39028d33e660fbd0321ea045488ba8ba8b9783))

## [2.6.0](https://github.com/alexgorbatchev/eslint-import-resolver-typescript/compare/v2.5.0...v2.6.0) (2022-03-23)


2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-import-resolver-typescript",
"version": "2.6.0",
"version": "2.7.0",
"description": "TypeScript .ts .tsx module resolver for `eslint-plugin-import`.",
"repository": "https://github.com/alexgorbatchev/eslint-import-resolver-typescript",
"author": "Alex Gorbatchev <alex.gorbatchev@gmail.com>",
109 changes: 90 additions & 19 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -9,19 +9,24 @@ import {
createMatchPath,
loadConfig,
ConfigLoaderResult,
MatchPath,
} from 'tsconfig-paths'

const IMPORTER_NAME = 'eslint-import-resolver-typescript'

const log = debug(IMPORTER_NAME)

/**
* .mts, .cts, .d.mts, .d.cts, .mjs, .cjs are not included because .cjs and .mjs must be used explicitly.
*/
const defaultExtensions = [
'.ts',
'.tsx',
'.d.ts',
// eslint-disable-next-line node/no-deprecated-api, sonar/deprecation
...Object.keys(require.extensions),
'.js',
'.jsx',
'.json',
'.node',
]

export const interfaceVersion = 2
@@ -69,15 +74,16 @@ export function resolve(
initMappers(options)
const mappedPath = getMappedPath(source)
if (mappedPath) {
log('matched ts path:', mappedPath)
log('matched ts path:', mappedPath.path)
}

// note that even if we map the path, we still need to do a final resolve
let foundNodePath: string | null | undefined
try {
foundNodePath = tsResolve(mappedPath ?? source, {
foundNodePath = tsResolve(mappedPath?.path ?? source, {
...options,
extensions: options.extensions ?? defaultExtensions,
extensions:
mappedPath?.extensions ?? options.extensions ?? defaultExtensions,
basedir: path.dirname(path.resolve(file)),
packageFilter: options.packageFilter ?? packageFilterDefault,
})
@@ -126,17 +132,46 @@ function packageFilterDefault(pkg: Record<string, string>) {
return pkg
}

function resolveExtension(id: string) {
const idWithoutJsExt = removeJsExtension(id)

if (idWithoutJsExt === id) {
return
}

if (id.endsWith('.mjs')) {
return {
path: idWithoutJsExt,
extensions: ['.mts', '.d.mts'],
}
}

if (id.endsWith('.cjs')) {
return {
path: idWithoutJsExt,
extensions: ['.cts', '.d.cts'],
}
}

return {
path: idWithoutJsExt,
}
}

/**
* Like `sync` from `resolve` package, but considers that the module id
* could have a .js or .jsx extension.
*/
function tsResolve(id: string, opts?: SyncOpts): string {
function tsResolve(id: string, opts: SyncOpts): string {
try {
return sync(id, opts)
} catch (error) {
const idWithoutJsExt = removeJsExtension(id)
if (idWithoutJsExt !== id) {
return sync(idWithoutJsExt, opts)
const resolved = resolveExtension(id)
if (resolved) {
return sync(resolved.path, {
...opts,
extensions: resolved.extensions ?? opts.extensions,
})
}
throw error
}
@@ -153,11 +188,20 @@ function removeQuerystring(id: string) {

/** Remove .js or .jsx extension from module id. */
function removeJsExtension(id: string) {
return id.replace(/\.jsx?$/, '')
return id.replace(/\.([cm]js|jsx?)$/, '')
}

let mappersBuildForOptions: TsResolverOptions
let mappers: Array<(source: string) => string | undefined> | undefined
let mappers:
| Array<
(source: string) =>
| {
path: string
extensions?: string[]
}
| undefined
>
| undefined

/**
* @param {string} source the module to resolve; i.e './some-module'
@@ -176,18 +220,45 @@ function getMappedPath(source: string) {

/**
* Like `createMatchPath` from `tsconfig-paths` package, but considers
* that the module id could have a .js or .jsx extension.
* that the module id could have a .mjs, .cjs, .js or .jsx extension.
*
* The default resolved path does not include the extension, so we need to return it for reusing,
* otherwise `.mts`, `.cts`, `.d.mts`, `.d.cts` will not be used by default, see also @link {defaultExtensions}.
*/
const createExtendedMatchPath: typeof createMatchPath = (...createArgs) => {
const createExtendedMatchPath: (
...createArgs: Parameters<typeof createMatchPath>
) => (...matchArgs: Parameters<MatchPath>) =>
| {
path: string
extensions?: string[]
}
| undefined = (...createArgs) => {
const matchPath = createMatchPath(...createArgs)

return (id, ...otherArgs) => {
const match = matchPath(id, ...otherArgs)
if (match != null) return match
return (id, readJson, fileExists, extensions) => {
const match = matchPath(id, readJson, fileExists, extensions)

if (match != null) {
return {
path: match,
}
}

const idWithoutJsExt = removeJsExtension(id)
if (idWithoutJsExt !== id) {
return matchPath(idWithoutJsExt, ...otherArgs)
const resolved = resolveExtension(id)

if (resolved) {
const match = matchPath(
resolved.path,
readJson,
fileExists,
resolved.extensions ?? extensions,
)
if (match) {
return {
path: match,
extensions: resolved.extensions,
}
}
}
}
}
2 changes: 2 additions & 0 deletions tests/withJsExtension/cjsImportee.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* eslint-env node */
module.exports = 'cjsImportee.cjs'
1 change: 1 addition & 0 deletions tests/withJsExtension/ctsImportee.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'ctsImportee.cts'
3 changes: 3 additions & 0 deletions tests/withJsExtension/d-ctsImportee.d.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare const content: 'yes'

export = content
3 changes: 3 additions & 0 deletions tests/withJsExtension/d-mtsImportee.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare const content: 'yes'

export default content
1 change: 1 addition & 0 deletions tests/withJsExtension/foo.cjs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'foo.cjs'
1 change: 1 addition & 0 deletions tests/withJsExtension/foo.mjs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'foo.mjs'
1 change: 1 addition & 0 deletions tests/withJsExtension/jsImportee.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'jsImportee.js'
1 change: 1 addition & 0 deletions tests/withJsExtension/jsxImportee.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'jsxImportee.jsx'
1 change: 1 addition & 0 deletions tests/withJsExtension/mjsImportee.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'mjsImportee.mjs'
1 change: 1 addition & 0 deletions tests/withJsExtension/mtsImportee.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'mtsImportee.mts'
42 changes: 42 additions & 0 deletions tests/withJsExtension/test.js
Original file line number Diff line number Diff line change
@@ -26,20 +26,38 @@ function assertResolve(id, relativePath) {

// import relative

assertResolve('./jsImportee.js', 'jsImportee.js')

assertResolve('./cjsImportee.cjs', 'cjsImportee.cjs')

assertResolve('./mjsImportee.mjs', 'mjsImportee.mjs')

assertResolve('./tsImportee.js', 'tsImportee.ts')

assertResolve('./tsxImportee.jsx', 'tsxImportee.tsx')

assertResolve('./ctsImportee.cjs', 'ctsImportee.cts')

assertResolve('./mtsImportee.mjs', 'mtsImportee.mts')

assertResolve('./dtsImportee.js', 'dtsImportee.d.ts')

assertResolve('./dtsImportee.jsx', 'dtsImportee.d.ts')

assertResolve('./d-ctsImportee.cjs', 'd-ctsImportee.d.cts')

assertResolve('./d-mtsImportee.mjs', 'd-mtsImportee.d.mts')

assertResolve('./foo', 'foo/index.ts')

assertResolve('./foo.js', 'foo.js/index.ts')

assertResolve('./foo.jsx', 'foo.jsx/index.ts')

assertResolve('./foo.cjs', 'foo.cjs/index.ts')

assertResolve('./foo.mjs', 'foo.mjs/index.ts')

assertResolve('./bar', 'bar/index.tsx')

// import using tsconfig.json path mapping
@@ -48,10 +66,22 @@ assertResolve('#/tsImportee.js', 'tsImportee.ts')

assertResolve('#/tsxImportee.jsx', 'tsxImportee.tsx')

assertResolve('#/cjsImportee.cjs', 'cjsImportee.cjs')

assertResolve('#/mjsImportee.mjs', 'mjsImportee.mjs')

assertResolve('#/ctsImportee.cjs', 'ctsImportee.cts')

assertResolve('#/mtsImportee.mjs', 'mtsImportee.mts')

assertResolve('#/dtsImportee.js', 'dtsImportee.d.ts')

assertResolve('#/dtsImportee.jsx', 'dtsImportee.d.ts')

assertResolve('#/d-ctsImportee.cjs', 'd-ctsImportee.d.cts')

assertResolve('#/d-mtsImportee.mjs', 'd-mtsImportee.d.mts')

assertResolve('#/foo', 'foo/index.ts')

assertResolve('#/foo.js', 'foo.js/index.ts')
@@ -66,10 +96,22 @@ assertResolve('tsImportee.js', 'tsImportee.ts')

assertResolve('tsxImportee.jsx', 'tsxImportee.tsx')

assertResolve('cjsImportee.cjs', 'cjsImportee.cjs')

assertResolve('mjsImportee.mjs', 'mjsImportee.mjs')

assertResolve('ctsImportee.cjs', 'ctsImportee.cts')

assertResolve('mtsImportee.mjs', 'mtsImportee.mts')

assertResolve('dtsImportee.js', 'dtsImportee.d.ts')

assertResolve('dtsImportee.jsx', 'dtsImportee.d.ts')

assertResolve('d-ctsImportee.cjs', 'd-ctsImportee.d.cts')

assertResolve('d-mtsImportee.mjs', 'd-mtsImportee.d.mts')

assertResolve('foo', 'foo/index.ts')

assertResolve('foo.js', 'foo.js/index.ts')