diff --git a/CHANGELOG.md b/CHANGELOG.md index 98c9b03..b2305f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.3.0](https://github.com/alexgorbatchev/eslint-import-resolver-typescript/compare/v2.2.1...v2.3.0) (2020-09-01) + + +### Features + +* import with .js and .jsx file extensions ([#56](https://github.com/alexgorbatchev/eslint-import-resolver-typescript/issues/56)) ([5340f96](https://github.com/alexgorbatchev/eslint-import-resolver-typescript/commit/5340f969fad38165f8cfb28025a5e15233d0e6f9)) + ### [2.2.1](https://github.com/alexgorbatchev/eslint-import-resolver-typescript/compare/v2.2.0...v2.2.1) (2020-08-14) diff --git a/package.json b/package.json index 36ab10f..52c20db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint-import-resolver-typescript", - "version": "2.2.1", + "version": "2.3.0", "description": "TypeScript .ts .tsx module resolver for `eslint-plugin-import`.", "repository": "https://github.com/alexgorbatchev/eslint-import-resolver-typescript", "author": "Alex Gorbatchev ", @@ -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" @@ -60,7 +61,7 @@ "@types/debug": "^4.1.5", "@types/glob": "^7.1.3", "@types/is-glob": "^4.0.1", - "@types/node": "^14.0.27", + "@types/node": "^14.6.2", "@types/resolve": "^1.17.1", "@types/unist": "^2.0.3", "dummy.js": "link:dummy.js", @@ -69,14 +70,14 @@ "react": "^16.13.1", "standard-version": "^8.0.2", "type-coverage": "^2.9.0", - "typescript": "^3.9.7", - "yarn-deduplicate": "^2.1.1" + "typescript": "^4.0.2", + "yarn-deduplicate": "^3.0.0" }, "resolutions": { "eslint-import-resolver-typescript": "link:.", - "prettier": "^2.0.5" + "prettier": "^2.1.1" }, "typeCoverage": { - "atLeast": 98.73 + "atLeast": 99.27 } } diff --git a/src/index.ts b/src/index.ts index 8260b1d..89ee9a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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' @@ -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, @@ -120,6 +120,27 @@ function packageFilterDefault(pkg: Record) { 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> @@ -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 @@ -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, ) diff --git a/tests/withJsExtension/.eslintrc.js b/tests/withJsExtension/.eslintrc.js new file mode 100644 index 0000000..47a5000 --- /dev/null +++ b/tests/withJsExtension/.eslintrc.js @@ -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', + }, + ], + }, +} diff --git a/tests/withJsExtension/bar/index.tsx b/tests/withJsExtension/bar/index.tsx new file mode 100644 index 0000000..4548a26 --- /dev/null +++ b/tests/withJsExtension/bar/index.tsx @@ -0,0 +1 @@ +export default 'bar' diff --git a/tests/withJsExtension/dtsImportee.d.ts b/tests/withJsExtension/dtsImportee.d.ts new file mode 100644 index 0000000..e645900 --- /dev/null +++ b/tests/withJsExtension/dtsImportee.d.ts @@ -0,0 +1,3 @@ +declare const content: 'yes' + +export default content diff --git a/tests/withJsExtension/foo.js/index.ts b/tests/withJsExtension/foo.js/index.ts new file mode 100644 index 0000000..6833cda --- /dev/null +++ b/tests/withJsExtension/foo.js/index.ts @@ -0,0 +1 @@ +export default 'foo.js' diff --git a/tests/withJsExtension/foo.jsx/index.ts b/tests/withJsExtension/foo.jsx/index.ts new file mode 100644 index 0000000..00001a5 --- /dev/null +++ b/tests/withJsExtension/foo.jsx/index.ts @@ -0,0 +1 @@ +export default 'foo.jsx' diff --git a/tests/withJsExtension/foo/index.ts b/tests/withJsExtension/foo/index.ts new file mode 100644 index 0000000..7e942cf --- /dev/null +++ b/tests/withJsExtension/foo/index.ts @@ -0,0 +1 @@ +export default 'foo' diff --git a/tests/withJsExtension/index.ts b/tests/withJsExtension/index.ts new file mode 100644 index 0000000..5406006 --- /dev/null +++ b/tests/withJsExtension/index.ts @@ -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' diff --git a/tests/withJsExtension/test.js b/tests/withJsExtension/test.js new file mode 100644 index 0000000..7fb8b42 --- /dev/null +++ b/tests/withJsExtension/test.js @@ -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') diff --git a/tests/withJsExtension/tsImportee.ts b/tests/withJsExtension/tsImportee.ts new file mode 100644 index 0000000..c824311 --- /dev/null +++ b/tests/withJsExtension/tsImportee.ts @@ -0,0 +1 @@ +export default 'tsImportee.ts' diff --git a/tests/withJsExtension/tsconfig.json b/tests/withJsExtension/tsconfig.json new file mode 100644 index 0000000..13d6d9f --- /dev/null +++ b/tests/withJsExtension/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "jsx": "react", + "baseUrl": "./", + "paths": { + "#/*": ["*"] + } + }, + "includes": ["./**/*"] +} diff --git a/tests/withJsExtension/tsxImportee.tsx b/tests/withJsExtension/tsxImportee.tsx new file mode 100644 index 0000000..2a39b98 --- /dev/null +++ b/tests/withJsExtension/tsxImportee.tsx @@ -0,0 +1 @@ +export default 'tsxImportee.tsx' diff --git a/yarn.lock b/yarn.lock index f7cc790..dfb2599 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2049,10 +2049,10 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= -"@types/node@*", "@types/node@^14.0.27": - version "14.0.27" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.27.tgz#a151873af5a5e851b51b3b065c9e63390a9e0eb1" - integrity sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g== +"@types/node@*", "@types/node@^14.6.2": + version "14.6.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.2.tgz#264b44c5a28dfa80198fc2f7b6d3c8a054b9491f" + integrity sha512-onlIwbaeqvZyniGPfdw/TEhKIh79pz66L1q06WUQqJLnAb6wbjvOtepLYTGHTqzdXgBYIE3ZdmqHDGsRsbBz7A== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -3267,6 +3267,11 @@ commander@^5.1.0: resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +commander@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.1.0.tgz#f8d722b78103141006b66f4c7ba1e97315ba75bc" + integrity sha512-wl7PNrYWd2y5mp1OK/LhTlv8Ff4kQJQRXXAvF+uU/TPNiVJUxZLRYGj/B0y/lPGAVcSbJqH2Za/cvHmrPMC8mA== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -7888,10 +7893,10 @@ prettier-plugin-toml@^0.3.1: "@toml-tools/parser" "^0.3.1" prettier "^1.16.0" -prettier@>=1.10, prettier@^1.16.0, prettier@^1.18.2, prettier@^2.0.4, prettier@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" - integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== +prettier@>=1.10, prettier@^1.16.0, prettier@^1.18.2, prettier@^2.0.4, prettier@^2.0.5, prettier@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.1.tgz#d9485dd5e499daa6cb547023b87a6cf51bee37d6" + integrity sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw== pretty-format@^26.1.0: version "26.1.0" @@ -10355,10 +10360,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.9.7: - version "3.9.7" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" - integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== +typescript@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" + integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== uglify-js@^2.6.1: version "2.8.29" @@ -10873,11 +10878,11 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" -yarn-deduplicate@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-2.1.1.tgz#500a3010e4bdee3c3250936e210910c7cae3d75d" - integrity sha512-lbFJGOMnqG/ncGjNBUt+JG4qfAGqeh8o9i4i5LXqNBdQ8ov8av6T1jizWQqr+zLPLCOqp/BYBZz8FymPQSR4RA== +yarn-deduplicate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/yarn-deduplicate/-/yarn-deduplicate-3.0.0.tgz#e5c219c48d687e3be79eff4c719d6d7ef372f094" + integrity sha512-Ps1JEurQJ7VHt4lFD1x7QLtnsfA0DxYzdz8jvu5q7mAw8JFgRC1Hqq46uC+NjSwf2wOlitTfbmWswMnnW2Gqfg== dependencies: "@yarnpkg/lockfile" "^1.1.0" - commander "^5.1.0" + commander "^6.1.0" semver "^7.3.2"