diff --git a/src/compile-dts.ts b/src/compile-dts.ts index f1ca6cd..2e26682 100644 --- a/src/compile-dts.ts +++ b/src/compile-dts.ts @@ -1,4 +1,3 @@ -import * as path from 'path'; import * as ts from 'typescript'; import { verboseLog, warnLog } from './logger'; @@ -12,6 +11,25 @@ export interface CompileDtsResult { rootFilesRemapping: Map; } +const declarationExtsRemapping: Record = { + [ts.Extension.Js]: ts.Extension.Js, + [ts.Extension.Jsx]: ts.Extension.Jsx, + [ts.Extension.Json]: ts.Extension.Json, + [ts.Extension.TsBuildInfo]: ts.Extension.TsBuildInfo, + [ts.Extension.Mjs]: ts.Extension.Mjs, + [ts.Extension.Cjs]: ts.Extension.Cjs, + + [ts.Extension.Ts]: ts.Extension.Dts, + [ts.Extension.Tsx]: ts.Extension.Dts, + [ts.Extension.Dts]: ts.Extension.Dts, + + [ts.Extension.Mts]: ts.Extension.Dmts, + [ts.Extension.Dmts]: ts.Extension.Dmts, + + [ts.Extension.Cts]: ts.Extension.Dcts, + [ts.Extension.Dcts]: ts.Extension.Dcts, +} satisfies Record; + export function compileDts(rootFiles: readonly string[], preferredConfigPath?: string, followSymlinks: boolean = true): CompileDtsResult { const compilerOptions = getCompilerOptions(rootFiles, preferredConfigPath); @@ -41,13 +59,16 @@ export function compileDts(rootFiles: readonly string[], preferredConfigPath?: s host.resolveModuleNameLiterals = (moduleLiterals: readonly ts.StringLiteralLike[], containingFile: string): ts.ResolvedModuleWithFailedLookupLocations[] => { return moduleLiterals.map((moduleLiteral: ts.StringLiteralLike): ts.ResolvedModuleWithFailedLookupLocations => { const resolvedModule = ts.resolveModuleName(moduleLiteral.text, containingFile, compilerOptions, host).resolvedModule; - // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison - if (resolvedModule && !resolvedModule.isExternalLibraryImport && resolvedModule.extension !== ts.Extension.Dts) { - resolvedModule.extension = ts.Extension.Dts; + if (resolvedModule && !resolvedModule.isExternalLibraryImport) { + const newExt = declarationExtsRemapping[resolvedModule.extension]; - verboseLog(`Change module from .ts to .d.ts: ${resolvedModule.resolvedFileName}`); + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison + if (newExt !== resolvedModule.extension) { + verboseLog(`Changing module from ${resolvedModule.extension} to ${newExt} for ${resolvedModule.resolvedFileName}`); - resolvedModule.resolvedFileName = changeExtensionToDts(resolvedModule.resolvedFileName); + resolvedModule.extension = newExt; + resolvedModule.resolvedFileName = changeExtensionToDts(resolvedModule.resolvedFileName); + } } return { resolvedModule }; @@ -82,13 +103,28 @@ export function compileDts(rootFiles: readonly string[], preferredConfigPath?: s } function changeExtensionToDts(fileName: string): string { - if (fileName.slice(-5) === '.d.ts') { + let ext: ts.Extension | undefined; + + // `path.extname` doesn't handle `.d.ts` cases (it returns `.ts` instead of `.d.ts`) + if (fileName.endsWith(ts.Extension.Dts)) { + return fileName; + } + + if (fileName.endsWith(ts.Extension.Cts)) { + ext = ts.Extension.Cts; + } else if (fileName.endsWith(ts.Extension.Mts)) { + ext = ts.Extension.Mts; + } else if (fileName.endsWith(ts.Extension.Ts)) { + ext = ts.Extension.Ts; + } else if (fileName.endsWith(ts.Extension.Tsx)) { + ext = ts.Extension.Tsx; + } + + if (ext === undefined) { return fileName; } - // .ts, .tsx - const ext = path.extname(fileName); - return fileName.slice(0, -ext.length) + '.d.ts'; + return fileName.slice(0, -ext.length) + declarationExtsRemapping[ext]; } /** diff --git a/tests/e2e/all-test-cases.ts b/tests/e2e/all-test-cases.ts index e8c2652..4236f75 100644 --- a/tests/e2e/all-test-cases.ts +++ b/tests/e2e/all-test-cases.ts @@ -28,6 +28,30 @@ function prepareString(str: string): string { return str.trim().replace(/\r\n/g, '\n'); } +function findInputFile(testCaseDir: string): string { + const tsFilePath = path.join(testCaseDir, 'input.ts'); + if (fs.existsSync(tsFilePath)) { + return tsFilePath; + } + + const dtsFilePath = path.join(testCaseDir, 'input.d.ts'); + if (fs.existsSync(dtsFilePath)) { + return dtsFilePath; + } + + const mtsFilePath = path.join(testCaseDir, 'input.mts'); + if (fs.existsSync(mtsFilePath)) { + return mtsFilePath; + } + + const ctsFilePath = path.join(testCaseDir, 'input.cts'); + if (fs.existsSync(ctsFilePath)) { + return ctsFilePath; + } + + throw new Error(`Cannot find input file in ${testCaseDir}`); +} + function getTestCases(): TestCase[] { return fs.readdirSync(testCasesDir) .filter((filePath: string) => { @@ -35,14 +59,10 @@ function getTestCases(): TestCase[] { }) .map((directoryName: string) => { const testCaseDir = path.resolve(testCasesDir, directoryName); - const outputFileName = path.resolve(testCaseDir, 'output.d.ts'); - - const tsFilePath = path.relative(process.cwd(), path.resolve(testCaseDir, 'input.ts')); - const dtsFilePath = path.relative(process.cwd(), path.resolve(testCaseDir, 'input.d.ts')); - const inputFileName = fs.existsSync(tsFilePath) ? tsFilePath : dtsFilePath; + const inputFileName = findInputFile(testCaseDir); - assert(fs.existsSync(inputFileName), `Input file doesn't exist for ${directoryName}`); + const outputFileName = path.resolve(testCaseDir, 'output.d.ts'); assert(fs.existsSync(outputFileName), `Output file doesn't exist for ${directoryName}`); const result: TestCase = { diff --git a/tests/e2e/test-cases/cts-extension/config.ts b/tests/e2e/test-cases/cts-extension/config.ts new file mode 100644 index 0000000..f1fb1a8 --- /dev/null +++ b/tests/e2e/test-cases/cts-extension/config.ts @@ -0,0 +1,5 @@ +import { TestCaseConfig } from '../../test-cases/test-case-config'; + +const config: TestCaseConfig = {}; + +export = config; diff --git a/tests/e2e/test-cases/cts-extension/decl.d.cts b/tests/e2e/test-cases/cts-extension/decl.d.cts new file mode 100644 index 0000000..32c01d6 --- /dev/null +++ b/tests/e2e/test-cases/cts-extension/decl.d.cts @@ -0,0 +1 @@ +export type Decl = string; diff --git a/tests/e2e/test-cases/cts-extension/file.cts b/tests/e2e/test-cases/cts-extension/file.cts new file mode 100644 index 0000000..ef1ff89 --- /dev/null +++ b/tests/e2e/test-cases/cts-extension/file.cts @@ -0,0 +1 @@ +export interface Interface {} diff --git a/tests/e2e/test-cases/cts-extension/input.cts b/tests/e2e/test-cases/cts-extension/input.cts new file mode 100644 index 0000000..32954d0 --- /dev/null +++ b/tests/e2e/test-cases/cts-extension/input.cts @@ -0,0 +1,6 @@ +import { Interface } from './file.cjs'; +import { Decl } from './decl.cjs'; + +export interface ExportedInterface extends Interface { + foo: Decl; +} diff --git a/tests/e2e/test-cases/cts-extension/output.d.ts b/tests/e2e/test-cases/cts-extension/output.d.ts new file mode 100644 index 0000000..2507e88 --- /dev/null +++ b/tests/e2e/test-cases/cts-extension/output.d.ts @@ -0,0 +1,8 @@ +export interface Interface { +} +export type Decl = string; +export interface ExportedInterface extends Interface { + foo: Decl; +} + +export {}; diff --git a/tests/e2e/test-cases/mts-extension/config.ts b/tests/e2e/test-cases/mts-extension/config.ts new file mode 100644 index 0000000..f1fb1a8 --- /dev/null +++ b/tests/e2e/test-cases/mts-extension/config.ts @@ -0,0 +1,5 @@ +import { TestCaseConfig } from '../../test-cases/test-case-config'; + +const config: TestCaseConfig = {}; + +export = config; diff --git a/tests/e2e/test-cases/mts-extension/decl.d.mts b/tests/e2e/test-cases/mts-extension/decl.d.mts new file mode 100644 index 0000000..32c01d6 --- /dev/null +++ b/tests/e2e/test-cases/mts-extension/decl.d.mts @@ -0,0 +1 @@ +export type Decl = string; diff --git a/tests/e2e/test-cases/mts-extension/file.mts b/tests/e2e/test-cases/mts-extension/file.mts new file mode 100644 index 0000000..ef1ff89 --- /dev/null +++ b/tests/e2e/test-cases/mts-extension/file.mts @@ -0,0 +1 @@ +export interface Interface {} diff --git a/tests/e2e/test-cases/mts-extension/input.mts b/tests/e2e/test-cases/mts-extension/input.mts new file mode 100644 index 0000000..6f75406 --- /dev/null +++ b/tests/e2e/test-cases/mts-extension/input.mts @@ -0,0 +1,6 @@ +import { Interface } from './file.mjs'; +import { Decl } from './decl.mjs'; + +export interface ExportedInterface extends Interface { + foo: Decl; +} diff --git a/tests/e2e/test-cases/mts-extension/output.d.ts b/tests/e2e/test-cases/mts-extension/output.d.ts new file mode 100644 index 0000000..2507e88 --- /dev/null +++ b/tests/e2e/test-cases/mts-extension/output.d.ts @@ -0,0 +1,8 @@ +export interface Interface { +} +export type Decl = string; +export interface ExportedInterface extends Interface { + foo: Decl; +} + +export {};