diff --git a/src/ExportMap.js b/src/ExportMap.js index 243b885a7..ebeb4fad7 100644 --- a/src/ExportMap.js +++ b/src/ExportMap.js @@ -464,6 +464,8 @@ ExportMap.parse = function (path, content, context) { case 'ClassDeclaration': case 'TypeAlias': // flowtype with babel-eslint parser case 'InterfaceDeclaration': + case 'DeclareFunction': + case 'TSDeclareFunction': case 'TSEnumDeclaration': case 'TSTypeAliasDeclaration': case 'TSInterfaceDeclaration': @@ -509,6 +511,34 @@ ExportMap.parse = function (path, content, context) { m.reexports.set(s.exported.name, { local, getImport: () => resolveImport(nsource) }) }) } + + // This doesn't declare anything, but changes what's being exported. + if (n.type === 'TSExportAssignment') { + const moduleDecl = ast.body.find((bodyNode) => + bodyNode.type === 'TSModuleDeclaration' && bodyNode.id.name === n.expression.name + ) + if (moduleDecl && moduleDecl.body && moduleDecl.body.body) { + moduleDecl.body.body.forEach((moduleBlockNode) => { + // Export-assignment exports all members in the namespace, explicitly exported or not. + const exportedDecl = moduleBlockNode.type === 'ExportNamedDeclaration' ? + moduleBlockNode.declaration : + moduleBlockNode + + if (exportedDecl.type === 'VariableDeclaration') { + exportedDecl.declarations.forEach((decl) => + recursivePatternCapture(decl.id,(id) => m.namespace.set( + id.name, + captureDoc(source, docStyleParsers, decl, exportedDecl, moduleBlockNode)) + ) + ) + } else { + m.namespace.set( + exportedDecl.id.name, + captureDoc(source, docStyleParsers, moduleBlockNode)) + } + }) + } + } }) return m diff --git a/tests/files/typescript-declare.d.ts b/tests/files/typescript-declare.d.ts new file mode 100644 index 000000000..5d526b85b --- /dev/null +++ b/tests/files/typescript-declare.d.ts @@ -0,0 +1,33 @@ +export declare type MyType = string +export declare enum MyEnum { + Foo, + Bar, + Baz +} +export declare interface Foo { + native: string | number + typedef: MyType + enum: MyEnum +} + +export declare abstract class Bar { + abstract foo(): Foo + + method(); +} + +export declare function getFoo() : MyType; + +export declare module MyModule { + export function ModuleFunction(); +} + +export declare namespace MyNamespace { + export function NamespaceFunction(); + + export module NSModule { + export function NSModuleFunction(); + } +} + +interface NotExported {} diff --git a/tests/files/typescript-export-assign.d.ts b/tests/files/typescript-export-assign.d.ts new file mode 100644 index 000000000..7a3392ee0 --- /dev/null +++ b/tests/files/typescript-export-assign.d.ts @@ -0,0 +1,39 @@ +export = AssignedNamespace; + +declare namespace AssignedNamespace { + type MyType = string + enum MyEnum { + Foo, + Bar, + Baz + } + + interface Foo { + native: string | number + typedef: MyType + enum: MyEnum + } + + abstract class Bar { + abstract foo(): Foo + + method(); + } + + export function getFoo() : MyType; + + export module MyModule { + export function ModuleFunction(); + } + + export namespace MyNamespace { + export function NamespaceFunction(); + + export module NSModule { + export function NSModuleFunction(); + } + } + + // Export-assignment exports all members in the namespace, explicitly exported or not. + // interface NotExported {} +} diff --git a/tests/src/rules/named.js b/tests/src/rules/named.js index 922914f90..8d8bd41c1 100644 --- a/tests/src/rules/named.js +++ b/tests/src/rules/named.js @@ -292,98 +292,100 @@ context('Typescript', function () { } parsers.forEach((parser) => { - ruleTester.run('named', rule, { - valid: [ - test({ - code: 'import { MyType } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { Foo } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { Bar } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { getFoo } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: 'import { MyEnum } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: ` - import { MyModule } from "./typescript" - MyModule.ModuleFunction() - `, - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - test({ - code: ` - import { MyNamespace } from "./typescript" - MyNamespace.NSModule.NSModuleFunction() - `, - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - }), - ], - - invalid: [ - test({ - code: 'import { MissingType } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - errors: [{ - message: "MissingType not found in './typescript'", - type: 'Identifier', - }], - }), - test({ - code: 'import { NotExported } from "./typescript"', - parser: parser, - settings: { - 'import/parsers': { [parser]: ['.ts'] }, - 'import/resolver': { 'eslint-import-resolver-typescript': true }, - }, - errors: [{ - message: "NotExported not found in './typescript'", - type: 'Identifier', - }], - }), - ], + ['typescript', 'typescript-declare', 'typescript-export-assign'].forEach((source) => { + ruleTester.run(`named`, rule, { + valid: [ + test({ + code: `import { MyType } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import { Foo } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import { Bar } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import { getFoo } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: `import { MyEnum } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: ` + import { MyModule } from "./${source}" + MyModule.ModuleFunction() + `, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + test({ + code: ` + import { MyNamespace } from "./${source}" + MyNamespace.NSModule.NSModuleFunction() + `, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + }), + ], + + invalid: [ + test({ + code: `import { MissingType } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: [{ + message: `MissingType not found in './${source}'`, + type: 'Identifier', + }], + }), + test({ + code: `import { NotExported } from "./${source}"`, + parser: parser, + settings: { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }, + errors: [{ + message: `NotExported not found in './${source}'`, + type: 'Identifier', + }], + }), + ], + }) }) }) }) diff --git a/utils/unambiguous.js b/utils/unambiguous.js index a8e842cac..1dae1d616 100644 --- a/utils/unambiguous.js +++ b/utils/unambiguous.js @@ -2,7 +2,7 @@ exports.__esModule = true -const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*]))/m +const pattern = /(^|;)\s*(export|import)((\s+\w)|(\s*[{*=]))/m /** * detect possible imports/exports without a full parse. * @@ -18,7 +18,7 @@ exports.test = function isMaybeUnambiguousModule(content) { } // future-/Babel-proof at the expense of being a little loose -const unambiguousNodeType = /^(Exp|Imp)ort.*Declaration$/ +const unambiguousNodeType = /^(?:(?:Exp|Imp)ort.*Declaration|TSExportAssignment)$/ /** * Given an AST, return true if the AST unambiguously represents a module.