Skip to content

Commit

Permalink
feat: import with .js and .jsx file extensions (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
gcangussu committed Sep 1, 2020
1 parent 32b1529 commit 5340f96
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 4 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"test": "run-p test:*",
"test:multipleEslintrcs": "eslint --ext ts,tsx tests/multipleEslintrcs",
"test:multipleTsconfigs": "eslint --ext ts,tsx tests/multipleTsconfigs",
"test:withJsExtension": "node tests/withJsExtension/test.js && eslint --ext ts,tsx tests/withJsExtension",
"test:withPaths": "eslint --ext ts,tsx tests/withPaths",
"test:withoutPaths": "eslint --ext ts,tsx tests/withoutPaths",
"type-coverage": "type-coverage --cache --detail --ignore-catch --strict --update"
Expand Down Expand Up @@ -77,6 +78,6 @@
"prettier": "^2.0.5"
},
"typeCoverage": {
"atLeast": 98.73
"atLeast": 99.27
}
}
45 changes: 42 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from 'tsconfig-paths'
import { sync as globSync } from 'glob'
import isGlob from 'is-glob'
import { isCore, sync } from 'resolve'
import { isCore, sync, SyncOpts } from 'resolve'
import debug from 'debug'

const IMPORTER_NAME = 'eslint-import-resolver-typescript'
Expand Down Expand Up @@ -70,7 +70,7 @@ export function resolve(
// note that even if we map the path, we still need to do a final resolve
let foundNodePath: string | null | undefined
try {
foundNodePath = sync(mappedPath || source, {
foundNodePath = tsResolve(mappedPath || source, {
extensions: options.extensions || defaultExtensions,
basedir: path.dirname(path.resolve(file)),
packageFilter: options.packageFilter || packageFilterDefault,
Expand Down Expand Up @@ -120,6 +120,27 @@ function packageFilterDefault(pkg: Record<string, string>) {
return pkg
}

/**
* 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 {
try {
return sync(id, opts)
} catch (error) {
const idWithoutJsExt = removeJsExtension(id)
if (idWithoutJsExt !== id) {
return sync(idWithoutJsExt, opts)
}
throw error
}
}

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

let mappersBuildForOptions: TsResolverOptions
let mappers:
| Array<(source: string, file: string) => string | undefined>
Expand All @@ -142,6 +163,24 @@ function getMappedPath(source: string, file: string) {
return paths[0]
}

/**
* Like `createMatchPath` from `tsconfig-paths` package, but considers
* that the module id could have a .js or .jsx extension.
*/
const createExtendedMatchPath: typeof createMatchPath = (...createArgs) => {
const matchPath = createMatchPath(...createArgs)

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

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

function initMappers(options: TsResolverOptions) {
if (mappers && mappersBuildForOptions === options) {
return
Expand Down Expand Up @@ -175,7 +214,7 @@ function initMappers(options: TsResolverOptions) {
// eslint-disable-next-line unicorn/no-fn-reference-in-iterator
.filter(isConfigLoaderSuccessResult)
.map(configLoaderResult => {
const matchPath = createMatchPath(
const matchPath = createExtendedMatchPath(
configLoaderResult.absoluteBaseUrl,
configLoaderResult.paths,
)
Expand Down
18 changes: 18 additions & 0 deletions tests/withJsExtension/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-env node */
/* eslint-disable @typescript-eslint/no-var-requires */
const config = require('../baseEslintConfig')(__dirname)

module.exports = {
...config,
rules: {
...config.rules,
'import/extensions': [
2,
'ignorePackages',
{
ts: 'never',
tsx: 'never',
},
],
},
}
1 change: 1 addition & 0 deletions tests/withJsExtension/bar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'bar'
3 changes: 3 additions & 0 deletions tests/withJsExtension/dtsImportee.d.ts
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.js/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'foo.js'
1 change: 1 addition & 0 deletions tests/withJsExtension/foo.jsx/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'foo.jsx'
1 change: 1 addition & 0 deletions tests/withJsExtension/foo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'foo'
33 changes: 33 additions & 0 deletions tests/withJsExtension/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// import relative
import './tsImportee.js'
import './tsxImportee.jsx'
import './dtsImportee.js'
import './dtsImportee.jsx'
import './foo'
import './foo.js'
import './foo.jsx'
import './bar'

// import using tsconfig.json path mapping
import '#/tsImportee.js'
import '#/tsxImportee.jsx'
import '#/dtsImportee.js'
import '#/dtsImportee.jsx'
import '#/foo'
import '#/foo.js'
import '#/foo.jsx'
import '#/bar'

// import using tsconfig.json base url
import 'tsImportee.js'
import 'tsxImportee.jsx'
import 'dtsImportee.js'
import 'dtsImportee.jsx'
import 'foo'
import 'foo.js'
import 'foo.jsx'
import 'bar'

// import from node_module
import 'typescript/lib/typescript.js'
import 'dummy.js'
88 changes: 88 additions & 0 deletions tests/withJsExtension/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* eslint-env node */
/* eslint-disable @typescript-eslint/no-var-requires */

const path = require('path')
const assert = require('assert')

const { resolve } = require('../../')

const config = {
project: path.join(__dirname, 'tsconfig.json'),
}

const file = path.join(__dirname, 'index.ts')

function assertResolve(id, relativePath) {
const filePath = path.join(__dirname, relativePath)
assert.deepStrictEqual(resolve(id, file, config), {
found: true,
path: filePath,
})
assert.deepStrictEqual(
resolve(id, file, { ...config, alwaysTryTypes: true }),
{ found: true, path: filePath },
)
}

// import relative

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

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

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

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

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

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

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

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

// import using tsconfig.json path mapping

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

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

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

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

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

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

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

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

// import using tsconfig.json base url

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

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

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

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

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

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

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

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

// import from node_module

assertResolve(
'typescript/lib/typescript.js',
'../../node_modules/typescript/lib/typescript.js',
)

assertResolve('dummy.js', '../../node_modules/dummy.js/index.js')
1 change: 1 addition & 0 deletions tests/withJsExtension/tsImportee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'tsImportee.ts'
10 changes: 10 additions & 0 deletions tests/withJsExtension/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"jsx": "react",
"baseUrl": "./",
"paths": {
"#/*": ["*"]
}
},
"includes": ["./**/*"]
}
1 change: 1 addition & 0 deletions tests/withJsExtension/tsxImportee.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default 'tsxImportee.tsx'

0 comments on commit 5340f96

Please sign in to comment.