Skip to content

Commit

Permalink
[New] import/default: support default export in TSExportAssignment
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxim-Mazurok authored and ljharb committed Mar 19, 2020
1 parent 0547c7e commit 0b585a1
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 11 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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])
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -108,7 +108,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": [
Expand Down
45 changes: 35 additions & 10 deletions src/ExportMap.js
Expand Up @@ -13,6 +13,12 @@ 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'

import {parseConfigFileTextToJson} from 'typescript'

const log = debug('eslint-plugin-import:ExportMap')

const exportCache = new Map()
Expand Down Expand Up @@ -445,8 +451,23 @@ ExportMap.parse = function (path, content, context) {

const source = makeSourceCode(content, ast)

ast.body.forEach(function (n) {
function isEsModuleInterop() {
const tsConfigInfo = tsConfigLoader({
cwd: context.parserOptions && context.parserOptions.tsconfigRootDir || process.cwd(),
getEnv: (key) => process.env[key],
})
try {
if (tsConfigInfo.tsConfigPath !== undefined) {
const jsonText = fs.readFileSync(tsConfigInfo.tsConfigPath).toString()
const tsConfig = parseConfigFileTextToJson(tsConfigInfo.tsConfigPath, jsonText).config
return tsConfig.compilerOptions.esModuleInterop
}
} catch (e) {
return false
}
}

ast.body.forEach(function (n) {
if (n.type === 'ExportDefaultDeclaration') {
const exportMeta = captureDoc(source, docStyleParsers, n)
if (n.declaration.type === 'Identifier') {
Expand Down Expand Up @@ -528,9 +549,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',
Expand All @@ -541,18 +567,17 @@ 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') {
Expand Down
3 changes: 3 additions & 0 deletions tests/files/typescript-export-as-default-namespace/index.d.ts
@@ -0,0 +1,3 @@
export as namespace Foo

export function bar(): void
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"esModuleInterop": true
}
}
@@ -0,0 +1,3 @@
export = FooBar;

declare namespace FooBar {}
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"esModuleInterop": true
}
}
41 changes: 41 additions & 0 deletions tests/src/rules/default.js
@@ -1,3 +1,4 @@
import path from 'path'
import { test, SYNTAX_CASES, getTSParsers } from '../utils'
import { RuleTester } from 'eslint'

Expand Down Expand Up @@ -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: [
Expand All @@ -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".'],
}),
],
})
})
Expand Down

0 comments on commit 0b585a1

Please sign in to comment.