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 e1ed323 commit e6debc8
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 10 deletions.
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -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": [
Expand Down
42 changes: 33 additions & 9 deletions src/ExportMap.js
Expand Up @@ -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()
Expand Down Expand Up @@ -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') {
Expand Down Expand Up @@ -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',
Expand All @@ -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') {
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 e6debc8

Please sign in to comment.