diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 47fd8d85cc5..2548d12c125 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -646,6 +646,10 @@ namespace ts { return resolveName(location, escapeLeadingUnderscores(name), meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isUse*/ false, excludeGlobals); }, getJsxNamespace: n => unescapeLeadingUnderscores(getJsxNamespace(n)), + getJsxFragmentFactory: n => { + const jsxFragmentFactory = getJsxFragmentFactoryEntity(n); + return jsxFragmentFactory && unescapeLeadingUnderscores(getFirstIdentifier(jsxFragmentFactory).escapedText); + }, getAccessibleSymbolChain, getTypePredicateOfSignature, resolveExternalModuleName: moduleSpecifierIn => { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8fc3a1457cd..7c9038ed3ba 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4131,6 +4131,7 @@ namespace ts { /* @internal */ getAllPossiblePropertiesOfTypes(type: readonly Type[]): Symbol[]; /* @internal */ resolveName(name: string, location: Node | undefined, meaning: SymbolFlags, excludeGlobals: boolean): Symbol | undefined; /* @internal */ getJsxNamespace(location?: Node): string; + /* @internal */ getJsxFragmentFactory(location: Node): string | undefined; /** * Note that this will return undefined in the following case: diff --git a/src/services/organizeImports.ts b/src/services/organizeImports.ts index 46eda4b00a9..c67b9df12e4 100644 --- a/src/services/organizeImports.ts +++ b/src/services/organizeImports.ts @@ -86,6 +86,7 @@ namespace ts.OrganizeImports { function removeUnusedImports(oldImports: readonly ImportDeclaration[], sourceFile: SourceFile, program: Program) { const typeChecker = program.getTypeChecker(); const jsxNamespace = typeChecker.getJsxNamespace(sourceFile); + const jsxFragmentFactory = typeChecker.getJsxFragmentFactory(sourceFile); const jsxElementsPresent = !!(sourceFile.transformFlags & TransformFlags.ContainsJsx); const usedImports: ImportDeclaration[] = []; @@ -150,7 +151,8 @@ namespace ts.OrganizeImports { function isDeclarationUsed(identifier: Identifier) { // The JSX factory symbol is always used if JSX elements are present - even if they are not allowed. - return jsxElementsPresent && (identifier.text === jsxNamespace) || FindAllReferences.Core.isSymbolReferencedInFile(identifier, typeChecker, sourceFile); + return jsxElementsPresent && (identifier.text === jsxNamespace || jsxFragmentFactory && identifier.text === jsxFragmentFactory) || + FindAllReferences.Core.isSymbolReferencedInFile(identifier, typeChecker, sourceFile); } } diff --git a/src/testRunner/unittests/services/organizeImports.ts b/src/testRunner/unittests/services/organizeImports.ts index 3e2b1108931..13f3f126c9e 100644 --- a/src/testRunner/unittests/services/organizeImports.ts +++ b/src/testRunner/unittests/services/organizeImports.ts @@ -757,6 +757,24 @@ export namespace React { } ); + testOrganizeImports("JsxFragmentPragmaTsx", + { + path: "/test.tsx", + content: `/** @jsx h */ +/** @jsxFrag frag */ +import { h, frag } from "@foo/core"; + +const elem = <>
Foo
; +`, + }, + { + path: "/@foo/core/index.d.ts", + content: `export function h(): void; +export function frag(): void; +` + } + ); + describe("Exports", () => { testOrganizeExports("MoveToTop", diff --git a/tests/baselines/reference/organizeImports/JsxFragmentPragmaTsx.ts b/tests/baselines/reference/organizeImports/JsxFragmentPragmaTsx.ts new file mode 100644 index 00000000000..1dbdfd05dc9 --- /dev/null +++ b/tests/baselines/reference/organizeImports/JsxFragmentPragmaTsx.ts @@ -0,0 +1,13 @@ +// ==ORIGINAL== +/** @jsx h */ +/** @jsxFrag frag */ +import { h, frag } from "@foo/core"; + +const elem = <>
Foo
; + +// ==ORGANIZED== +/** @jsx h */ +/** @jsxFrag frag */ +import { frag, h } from "@foo/core"; + +const elem = <>
Foo
;