From a9815dac72e667f1cb9d898022183c428111b2fa Mon Sep 17 00:00:00 2001 From: liuxingbaoyu <30521560+liuxingbaoyu@users.noreply.github.com> Date: Thu, 19 Sep 2024 21:01:07 +0800 Subject: [PATCH] [Fix] `export`: False positive for exported overloaded functions in TS --- CHANGELOG.md | 3 +++ src/rules/export.js | 48 +++++++++++++-------------------------- tests/src/rules/export.js | 26 +++++++++++++++++++-- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32899988a..1dbd46fc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Fixed - `ExportMap` / flat config: include `languageOptions` in context ([#3052], thanks [@michaelfaith]) - [`no-named-as-default`]: Allow using an identifier if the export is both a named and a default export ([#3032], thanks [@akwodkiewicz]) +- [`export`]: False positive for exported overloaded functions in TS ([#3065], thanks [@liuxingbaoyu]) ### Changed - [Docs] [`no-relative-packages`]: fix typo ([#3066], thanks [@joshuaobrien]) @@ -1140,6 +1141,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md [#3066]: https://github.com/import-js/eslint-plugin-import/pull/3066 +[#3065]: https://github.com/import-js/eslint-plugin-import/pull/3065 [#3052]: https://github.com/import-js/eslint-plugin-import/pull/3052 [#3043]: https://github.com/import-js/eslint-plugin-import/pull/3043 [#3036]: https://github.com/import-js/eslint-plugin-import/pull/3036 @@ -1875,6 +1877,7 @@ for info on changes for earlier releases. [@lilling]: https://github.com/lilling [@ljharb]: https://github.com/ljharb [@ljqx]: https://github.com/ljqx +[@liuxingbaoyu]: https://github.com/liuxingbaoyu [@lo1tuma]: https://github.com/lo1tuma [@loganfsmyth]: https://github.com/loganfsmyth [@luczsoma]: https://github.com/luczsoma diff --git a/src/rules/export.js b/src/rules/export.js index 197a0eb51..fbbc39d75 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -2,7 +2,6 @@ import ExportMapBuilder from '../exportMap/builder'; import recursivePatternCapture from '../exportMap/patternCapture'; import docsUrl from '../docsUrl'; import includes from 'array-includes'; -import flatMap from 'array.prototype.flatmap'; /* Notes on TypeScript namespaces aka TSModuleDeclaration: @@ -27,42 +26,25 @@ const rootProgram = 'root'; const tsTypePrefix = 'type:'; /** - * Detect function overloads like: + * remove function overloads like: * ```ts * export function foo(a: number); * export function foo(a: string); - * export function foo(a: number|string) { return a; } * ``` * @param {Set} nodes - * @returns {boolean} */ -function isTypescriptFunctionOverloads(nodes) { - const nodesArr = Array.from(nodes); - - const idents = flatMap( - nodesArr, - (node) => node.declaration && ( - node.declaration.type === 'TSDeclareFunction' // eslint 6+ - || node.declaration.type === 'TSEmptyBodyFunctionDeclaration' // eslint 4-5 - ) - ? node.declaration.id.name - : [], - ); - if (new Set(idents).size !== idents.length) { - return true; - } - - const types = new Set(nodesArr.map((node) => node.parent.type)); - if (!types.has('TSDeclareFunction')) { - return false; - } - if (types.size === 1) { - return true; - } - if (types.size === 2 && types.has('FunctionDeclaration')) { - return true; - } - return false; +function removeTypescriptFunctionOverloads(nodes) { + nodes.forEach((node) => { + const declType = node.type === 'ExportDefaultDeclaration' ? node.declaration.type : node.parent.type; + if ( + // eslint 6+ + declType === 'TSDeclareFunction' + // eslint 4-5 + || declType === 'TSEmptyBodyFunctionDeclaration' + ) { + nodes.delete(node); + } + }); } /** @@ -227,9 +209,11 @@ module.exports = { 'Program:exit'() { for (const [, named] of namespace) { for (const [name, nodes] of named) { + removeTypescriptFunctionOverloads(nodes); + if (nodes.size <= 1) { continue; } - if (isTypescriptFunctionOverloads(nodes) || isTypescriptNamespaceMerging(nodes)) { continue; } + if (isTypescriptNamespaceMerging(nodes)) { continue; } for (const node of nodes) { if (shouldSkipTypescriptNamespace(node, nodes)) { continue; } diff --git a/tests/src/rules/export.js b/tests/src/rules/export.js index a7f2bec12..f16a25ecf 100644 --- a/tests/src/rules/export.js +++ b/tests/src/rules/export.js @@ -56,6 +56,15 @@ ruleTester.run('export', rule, { `, parser, })), + getTSParsers().map((parser) => ({ + code: ` + export default function foo(param: string): boolean; + export default function foo(param: string, param1?: number): boolean { + return param && param1; + } + `, + parser, + })), ), invalid: [].concat( @@ -154,6 +163,19 @@ ruleTester.run('export', rule, { ecmaVersion: 2022, }, })), + + getTSParsers().map((parser) => ({ + code: ` + export default function a(): void; + export default function a() {} + export { x as default }; + `, + errors: [ + 'Multiple default exports.', + 'Multiple default exports.', + ], + parser, + })), ), }); @@ -510,7 +532,7 @@ context('TypeScript', function () { }), test({ code: ` - export function Foo(); + export function Foo() { }; export class Foo { } export namespace Foo { } `, @@ -529,7 +551,7 @@ context('TypeScript', function () { test({ code: ` export const Foo = 'bar'; - export function Foo(); + export function Foo() { }; export namespace Foo { } `, errors: [