diff --git a/package.json b/package.json index 0673205f3b..e035e776c0 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,8 @@ "minimatch": "^3.0.4", "object.values": "^1.1.0", "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" + "resolve": "^1.12.0", + "tsconfig-paths": "^3.9.0" }, "nyc": { "require": [ diff --git a/src/ExportMap.js b/src/ExportMap.js index 56f9f58b57..102b596167 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -13,6 +13,10 @@ import isIgnored, { hasValidExtension } from 'eslint-module-utils/ignore' import { hashObject } from 'eslint-module-utils/hash' import * as unambiguous from 'eslint-module-utils/unambiguous' +import { tsConfigLoader } from 'tsconfig-paths/lib/tsconfig-loader' + +import includes from 'array-includes' + const log = debug('eslint-plugin-import:ExportMap') const exportCache = new Map() @@ -445,6 +449,21 @@ ExportMap.parse = function (path, content, context) { const source = makeSourceCode(content, ast) + function isEsModuleInterop() { + const tsConfig = tsConfigLoader({ + cwd: context.parserOptions && context.parserOptions.tsconfigRootDir || process.cwd(), + getEnv: (key) => process.env[key], + }) + try { + if (tsConfig.tsConfigPath !== undefined) { + const json = fs.readFileSync(tsConfig.tsConfigPath) + return JSON.parse(json).compilerOptions.esModuleInterop + } + } catch (e) { + return false + } + } + ast.body.forEach(function (n) { if (n.type === 'ExportDefaultDeclaration') { @@ -528,9 +547,14 @@ ExportMap.parse = function (path, content, context) { }) } + const isEsModuleInteropTrue = isEsModuleInterop() + + const exports = ['TSExportAssignment'] + isEsModuleInteropTrue && exports.push('TSNamespaceExportDeclaration') + // This doesn't declare anything, but changes what's being exported. - if (n.type === 'TSExportAssignment') { - const exportedName = n.expression.name + if (includes(exports, n.type)) { + const exportedName = n.expression && n.expression.name || n.id.name const declTypes = [ 'VariableDeclaration', 'ClassDeclaration', @@ -541,18 +565,18 @@ ExportMap.parse = function (path, content, context) { 'TSAbstractClassDeclaration', 'TSModuleDeclaration', ] - const exportedDecls = ast.body.filter(({ type, id, declarations }) => - declTypes.includes(type) && - ( - (id && id.name === exportedName) || - (declarations && declarations.find(d => d.id.name === exportedName)) - ) - ) + const exportedDecls = ast.body.filter(({ type, id, declarations }) => includes(declTypes, type) && ( + (id && id.name === exportedName) || + (declarations && declarations.find(d => d.id.name === exportedName)) + )) if (exportedDecls.length === 0) { // Export is not referencing any local declaration, must be re-exporting m.namespace.set('default', captureDoc(source, docStyleParsers, n)) return } + if (isEsModuleInteropTrue) { + m.namespace.set('default', {}) + } exportedDecls.forEach((decl) => { if (decl.type === 'TSModuleDeclaration') { if (decl.body && decl.body.type === 'TSModuleDeclaration') { diff --git a/tests/files/typescript-export-as-default-namespace/index.d.ts b/tests/files/typescript-export-as-default-namespace/index.d.ts new file mode 100644 index 0000000000..953c3410b1 --- /dev/null +++ b/tests/files/typescript-export-as-default-namespace/index.d.ts @@ -0,0 +1,3 @@ +export as namespace Foo + +export function bar(): void diff --git a/tests/files/typescript-export-as-default-namespace/tsconfig.json b/tests/files/typescript-export-as-default-namespace/tsconfig.json new file mode 100644 index 0000000000..a72ee3e88b --- /dev/null +++ b/tests/files/typescript-export-as-default-namespace/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/tests/files/typescript-export-assign-default-namespace/index.d.ts b/tests/files/typescript-export-assign-default-namespace/index.d.ts new file mode 100644 index 0000000000..2ad4822f7c --- /dev/null +++ b/tests/files/typescript-export-assign-default-namespace/index.d.ts @@ -0,0 +1,3 @@ +export = FooBar; + +declare namespace FooBar {} diff --git a/tests/files/typescript-export-assign-default-namespace/tsconfig.json b/tests/files/typescript-export-assign-default-namespace/tsconfig.json new file mode 100644 index 0000000000..a72ee3e88b --- /dev/null +++ b/tests/files/typescript-export-assign-default-namespace/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js index 79d03e6c55..d3d4aae4a7 100644 --- a/tests/src/rules/default.js +++ b/tests/src/rules/default.js @@ -1,3 +1,4 @@ +import path from 'path' import { test, SYNTAX_CASES, getTSParsers } from '../utils' import { RuleTester } from 'eslint' @@ -189,6 +190,28 @@ context('TypeScript', function () { 'import/resolver': { 'eslint-import-resolver-typescript': true }, }, }), + test({ + code: `import React from "./typescript-export-assign-default-namespace"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + parserOptions: { + tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-assign-default-namespace/'), + }, + }), + test({ + code: `import Foo from "./typescript-export-as-default-namespace"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + parserOptions: { + tsconfigRootDir: path.resolve(__dirname, '../../files/typescript-export-as-default-namespace/'), + }, + }), ], invalid: [ @@ -201,6 +224,24 @@ context('TypeScript', function () { }, errors: ['No default export found in imported module "./typescript".'], }), + test({ + code: `import React from "./typescript-export-assign-default-namespace"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: ['No default export found in imported module "./typescript-export-assign-default-namespace".'], + }), + test({ + code: `import FooBar from "./typescript-export-as-default-namespace"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: ['No default export found in imported module "./typescript-export-as-default-namespace".'], + }), ], }) })