From 77e1791b0f55e3099287b51066c256d829dd0506 Mon Sep 17 00:00:00 2001 From: Maxim Mazurok Date: Thu, 19 Mar 2020 17:46:19 +1100 Subject: [PATCH] [New] `import/default`: support default export in TSExportAssignment --- CHANGELOG.md | 3 ++ package.json | 3 +- src/ExportMap.js | 42 +++++++++++++++---- .../index.d.ts | 3 ++ .../tsconfig.json | 5 +++ .../index.d.ts | 3 ++ .../tsconfig.json | 5 +++ tests/src/rules/default.js | 41 ++++++++++++++++++ 8 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 tests/files/typescript-export-as-default-namespace/index.d.ts create mode 100644 tests/files/typescript-export-as-default-namespace/tsconfig.json create mode 100644 tests/files/typescript-export-assign-default-namespace/index.d.ts create mode 100644 tests/files/typescript-export-assign-default-namespace/tsconfig.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ffc185b037..88a32dade4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel - [`import/default`]: support default export in TSExportAssignment ([#1528], thanks [@joaovieira]) - [`no-cycle`]: add `ignoreExternal` option ([#1681], thanks [@sveyret]) - [`order`]: Add support for TypeScript's "import equals"-expressions ([#1785], thanks [@manuth]) +- [`import/default`]: support default export in TSExportAssignment ([#1689], thanks [@Maxim-Mazurok]) ### Fixed - [`group-exports`]: Flow type export awareness ([#1702], thanks [@ernestostifano]) @@ -705,6 +706,7 @@ for info on changes for earlier releases. [#1702]: https://github.com/benmosher/eslint-plugin-import/issues/1702 [#1691]: https://github.com/benmosher/eslint-plugin-import/pull/1691 [#1690]: https://github.com/benmosher/eslint-plugin-import/pull/1690 +[#1689]: https://github.com/benmosher/eslint-plugin-import/pull/1689 [#1681]: https://github.com/benmosher/eslint-plugin-import/pull/1681 [#1676]: https://github.com/benmosher/eslint-plugin-import/pull/1676 [#1666]: https://github.com/benmosher/eslint-plugin-import/pull/1666 @@ -1189,3 +1191,4 @@ for info on changes for earlier releases. [@barbogast]: https://github.com/barbogast [@adamborowski]: https://github.com/adamborowski [@adjerbetian]: https://github.com/adjerbetian +[@Maxim-Mazurok]: https://github.com/Maxim-Mazurok 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".'], + }), ], }) })