From 694f1eca8cc3869e88b6434466f432b358962a42 Mon Sep 17 00:00:00 2001 From: Atharva Singh <162712062+atharvasingh7007@users.noreply.github.com> Date: Sun, 26 Apr 2026 13:33:20 +0530 Subject: [PATCH] fix: transformRequest input typing (#10745) * fix(types): type transformRequest input data * chore: imrpove testing and overall posture * fix:f failing tests --------- Co-authored-by: Jay --- index.d.cts | 10 ++-- index.d.ts | 10 ++-- tests/module/cjs/tests/helpers/fixture.cjs | 8 ++- .../tests/helpers/transform-request-typing.ts | 52 +++++++++++++++++++ .../tests/ts-require-default.module.test.cjs | 8 ++- .../cjs/tests/ts-require.module.test.cjs | 2 +- .../module/cjs/tests/typings.module.test.cjs | 22 +++++++- tests/module/esm/tests/helpers/fixture.js | 8 ++- .../tests/helpers/transform-request-typing.ts | 52 +++++++++++++++++++ tests/module/esm/tests/ts.module.test.js | 2 +- tests/module/esm/tests/typings.module.test.js | 38 ++++++++++++-- 11 files changed, 192 insertions(+), 20 deletions(-) create mode 100644 tests/module/cjs/tests/helpers/transform-request-typing.ts create mode 100644 tests/module/esm/tests/helpers/transform-request-typing.ts diff --git a/index.d.cts b/index.d.cts index 5afd23e9..81d2296d 100644 --- a/index.d.cts +++ b/index.d.cts @@ -320,14 +320,14 @@ declare namespace axios { type AxiosResponseHeaders = RawAxiosResponseHeaders & AxiosHeaders; - interface AxiosRequestTransformer { - (this: InternalAxiosRequestConfig, data: any, headers: AxiosRequestHeaders): any; + interface AxiosRequestTransformer { + (this: InternalAxiosRequestConfig, data: D, headers: AxiosRequestHeaders): any; } - interface AxiosResponseTransformer { + interface AxiosResponseTransformer { ( this: InternalAxiosRequestConfig, - data: any, + data: T, headers: AxiosResponseHeaders, status?: number ): any; @@ -471,7 +471,7 @@ declare namespace axios { method?: Method | string; baseURL?: string; allowAbsoluteUrls?: boolean; - transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[]; + transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[]; transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[]; headers?: (RawAxiosRequestHeaders & MethodsHeaders) | AxiosHeaders; params?: any; diff --git a/index.d.ts b/index.d.ts index 4bcfe9e4..5e2e18a4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -138,14 +138,14 @@ export type RawAxiosResponseHeaders = Partial { + (this: InternalAxiosRequestConfig, data: D, headers: AxiosRequestHeaders): any; } -export interface AxiosResponseTransformer { +export interface AxiosResponseTransformer { ( this: InternalAxiosRequestConfig, - data: any, + data: T, headers: AxiosResponseHeaders, status?: number ): any; @@ -367,7 +367,7 @@ export interface AxiosRequestConfig { method?: StringLiteralsOrString; baseURL?: string; allowAbsoluteUrls?: boolean; - transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[]; + transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[]; transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[]; headers?: (RawAxiosRequestHeaders & MethodsHeaders) | AxiosHeaders; params?: any; diff --git a/tests/module/cjs/tests/helpers/fixture.cjs b/tests/module/cjs/tests/helpers/fixture.cjs index 0d7e39dc..28080db2 100644 --- a/tests/module/cjs/tests/helpers/fixture.cjs +++ b/tests/module/cjs/tests/helpers/fixture.cjs @@ -1,13 +1,19 @@ const fs = require('fs'); const path = require('path'); -const createTempFixture = (suiteRoot, name, sourcePath, tsconfig, packageJson) => { +const createTempFixture = (suiteRoot, repoRoot, name, sourcePath, tsconfig, packageJson) => { const tempRoot = fs.mkdtempSync(path.join(suiteRoot, `.tmp-module-${name}-`)); const source = fs.readFileSync(sourcePath, 'utf8'); fs.writeFileSync(path.join(tempRoot, 'index.ts'), source); fs.writeFileSync(path.join(tempRoot, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2)); + if (repoRoot) { + const axiosFixturePath = path.join(tempRoot, 'node_modules', 'axios'); + fs.mkdirSync(path.dirname(axiosFixturePath), { recursive: true }); + fs.symlinkSync(repoRoot, axiosFixturePath, 'junction'); + } + if (packageJson) { fs.writeFileSync(path.join(tempRoot, 'package.json'), JSON.stringify(packageJson, null, 2)); } diff --git a/tests/module/cjs/tests/helpers/transform-request-typing.ts b/tests/module/cjs/tests/helpers/transform-request-typing.ts new file mode 100644 index 00000000..81cb00fe --- /dev/null +++ b/tests/module/cjs/tests/helpers/transform-request-typing.ts @@ -0,0 +1,52 @@ +import axios = require('axios'); + +type Payload = { + foo: string; + count: number; +}; + +type OtherPayload = { + other: number; +}; + +const transformer: axios.AxiosRequestTransformer = function (data, headers) { + headers.setContentType('application/json'); + data.foo.toUpperCase(); + data.count.toFixed(); + + // `this` is narrowed to InternalAxiosRequestConfig, so this.data is Payload + this.data?.foo.toUpperCase(); + this.data?.count.toFixed(); + // @ts-expect-error this.data is Payload, not OtherPayload + this.data?.other; + + // @ts-expect-error property does not exist on Payload + data.bar; + + return JSON.stringify(data); +}; + +const wrongTransformer: axios.AxiosRequestTransformer = (data) => JSON.stringify(data); + +const config: axios.AxiosRequestConfig = { + data: { + foo: 'hello', + count: 1, + }, + transformRequest: [ + transformer, + (data) => { + data.foo.toUpperCase(); + data.count.toFixed(); + + // @ts-expect-error property does not exist on Payload + data.bar; + + return JSON.stringify(data); + }, + // @ts-expect-error transformer payload type does not match config D + wrongTransformer, + ], +}; + +void axios.request(config); diff --git a/tests/module/cjs/tests/ts-require-default.module.test.cjs b/tests/module/cjs/tests/ts-require-default.module.test.cjs index ca72d38c..3be11f15 100644 --- a/tests/module/cjs/tests/ts-require-default.module.test.cjs +++ b/tests/module/cjs/tests/ts-require-default.module.test.cjs @@ -21,7 +21,13 @@ const tsconfig = { describe('module ts-require-default compatibility', () => { it('compiles and executes require("axios").default imports', () => { const sourcePath = path.join(repoRoot, 'tests/module/cjs/tests/helpers/ts-require-default.ts'); - const fixturePath = createTempFixture(suiteRoot, 'ts-require-default', sourcePath, tsconfig); + const fixturePath = createTempFixture( + suiteRoot, + null, + 'ts-require-default', + sourcePath, + tsconfig + ); try { runCommand('node', [tscBin, '-p', 'tsconfig.json'], { cwd: fixturePath }); diff --git a/tests/module/cjs/tests/ts-require.module.test.cjs b/tests/module/cjs/tests/ts-require.module.test.cjs index d1e3eec0..cd3891f5 100644 --- a/tests/module/cjs/tests/ts-require.module.test.cjs +++ b/tests/module/cjs/tests/ts-require.module.test.cjs @@ -21,7 +21,7 @@ const tsconfig = { describe('module ts-require compatibility', () => { it('compiles and executes require("axios") imports', () => { const sourcePath = path.join(repoRoot, 'tests/module/cjs/tests/helpers/ts-require.ts'); - const fixturePath = createTempFixture(suiteRoot, 'ts-require', sourcePath, tsconfig); + const fixturePath = createTempFixture(suiteRoot, null, 'ts-require', sourcePath, tsconfig); try { runCommand('node', [tscBin, '-p', 'tsconfig.json'], { cwd: fixturePath }); diff --git a/tests/module/cjs/tests/typings.module.test.cjs b/tests/module/cjs/tests/typings.module.test.cjs index b9936d78..25fa54c6 100644 --- a/tests/module/cjs/tests/typings.module.test.cjs +++ b/tests/module/cjs/tests/typings.module.test.cjs @@ -17,7 +17,27 @@ const tsconfig = { describe('module cjs typings compatibility', () => { it('type-checks commonjs axios typings', () => { const sourcePath = path.join(repoRoot, 'tests/module/cjs/tests/helpers/cjs-typing.ts'); - const fixturePath = createTempFixture(suiteRoot, 'typings-cjs', sourcePath, tsconfig); + const fixturePath = createTempFixture(suiteRoot, repoRoot, 'typings-cjs', sourcePath, tsconfig); + + try { + runCommand('node', [tscBin, '--noEmit', '-p', 'tsconfig.json'], { cwd: fixturePath }); + } finally { + cleanupTempFixture(fixturePath); + } + }); + + it('type-checks generic commonjs request transformer typings', () => { + const sourcePath = path.join( + repoRoot, + 'tests/module/cjs/tests/helpers/transform-request-typing.ts' + ); + const fixturePath = createTempFixture( + suiteRoot, + repoRoot, + 'typings-cjs-transform-request', + sourcePath, + tsconfig + ); try { runCommand('node', [tscBin, '--noEmit', '-p', 'tsconfig.json'], { cwd: fixturePath }); diff --git a/tests/module/esm/tests/helpers/fixture.js b/tests/module/esm/tests/helpers/fixture.js index 449523e7..6d3e39bf 100644 --- a/tests/module/esm/tests/helpers/fixture.js +++ b/tests/module/esm/tests/helpers/fixture.js @@ -1,13 +1,19 @@ import fs from 'node:fs'; import path from 'node:path'; -export const createTempFixture = (suiteRoot, name, sourcePath, tsconfig, packageJson) => { +export const createTempFixture = (suiteRoot, repoRoot, name, sourcePath, tsconfig, packageJson) => { const tempRoot = fs.mkdtempSync(path.join(suiteRoot, `.tmp-module-${name}-`)); const source = fs.readFileSync(sourcePath, 'utf8'); fs.writeFileSync(path.join(tempRoot, 'index.ts'), source); fs.writeFileSync(path.join(tempRoot, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2)); + if (repoRoot) { + const axiosFixturePath = path.join(tempRoot, 'node_modules', 'axios'); + fs.mkdirSync(path.dirname(axiosFixturePath), { recursive: true }); + fs.symlinkSync(repoRoot, axiosFixturePath, 'junction'); + } + if (packageJson) { fs.writeFileSync(path.join(tempRoot, 'package.json'), JSON.stringify(packageJson, null, 2)); } diff --git a/tests/module/esm/tests/helpers/transform-request-typing.ts b/tests/module/esm/tests/helpers/transform-request-typing.ts new file mode 100644 index 00000000..e5d843a4 --- /dev/null +++ b/tests/module/esm/tests/helpers/transform-request-typing.ts @@ -0,0 +1,52 @@ +import axios, { AxiosRequestConfig, AxiosRequestTransformer } from 'axios'; + +type Payload = { + foo: string; + count: number; +}; + +type OtherPayload = { + other: number; +}; + +const transformer: AxiosRequestTransformer = function (data, headers) { + headers.setContentType('application/json'); + data.foo.toUpperCase(); + data.count.toFixed(); + + // `this` is narrowed to InternalAxiosRequestConfig, so this.data is Payload + this.data?.foo.toUpperCase(); + this.data?.count.toFixed(); + // @ts-expect-error this.data is Payload, not OtherPayload + this.data?.other; + + // @ts-expect-error property does not exist on Payload + data.bar; + + return JSON.stringify(data); +}; + +const wrongTransformer: AxiosRequestTransformer = (data) => JSON.stringify(data); + +const config: AxiosRequestConfig = { + data: { + foo: 'hello', + count: 1, + }, + transformRequest: [ + transformer, + (data) => { + data.foo.toUpperCase(); + data.count.toFixed(); + + // @ts-expect-error property does not exist on Payload + data.bar; + + return JSON.stringify(data); + }, + // @ts-expect-error transformer payload type does not match config D + wrongTransformer, + ], +}; + +void axios.request(config); diff --git a/tests/module/esm/tests/ts.module.test.js b/tests/module/esm/tests/ts.module.test.js index 8639986c..addc8c12 100644 --- a/tests/module/esm/tests/ts.module.test.js +++ b/tests/module/esm/tests/ts.module.test.js @@ -22,7 +22,7 @@ const tsconfig = { describe('module ts compatibility', () => { it('compiles and executes import axios syntax', () => { const sourcePath = path.join(repoRoot, 'tests/module/esm/tests/helpers/esm-functions.ts'); - const fixturePath = createTempFixture(suiteRoot, 'ts', sourcePath, tsconfig, { + const fixturePath = createTempFixture(suiteRoot, null, 'ts', sourcePath, tsconfig, { type: 'commonjs', }); diff --git a/tests/module/esm/tests/typings.module.test.js b/tests/module/esm/tests/typings.module.test.js index 84a2a060..9b4b4df2 100644 --- a/tests/module/esm/tests/typings.module.test.js +++ b/tests/module/esm/tests/typings.module.test.js @@ -16,11 +16,41 @@ const tsconfig = { }; describe('module esm typings compatibility', () => { - it('type-checks esm axios typings', () => { + it('type-checks esm axios typings', { timeout: 20000 }, () => { const sourcePath = path.join(repoRoot, 'tests/module/esm/tests/helpers/esm-index.ts'); - const fixturePath = createTempFixture(suiteRoot, 'typings-esm', sourcePath, tsconfig, { - type: 'module', - }); + const fixturePath = createTempFixture( + suiteRoot, + repoRoot, + 'typings-esm', + sourcePath, + tsconfig, + { + type: 'module', + } + ); + + try { + runCommand('node', [tscBin, '--noEmit', '-p', 'tsconfig.json'], { cwd: fixturePath }); + } finally { + cleanupTempFixture(fixturePath); + } + }); + + it('type-checks generic request transformer typings', { timeout: 20000 }, () => { + const sourcePath = path.join( + repoRoot, + 'tests/module/esm/tests/helpers/transform-request-typing.ts' + ); + const fixturePath = createTempFixture( + suiteRoot, + repoRoot, + 'typings-esm-transform-request', + sourcePath, + tsconfig, + { + type: 'module', + } + ); try { runCommand('node', [tscBin, '--noEmit', '-p', 'tsconfig.json'], { cwd: fixturePath });