Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] import/default: support default export in TSExportAssignment #1689

Merged
merged 1 commit into from Jun 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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