From c9c541cb6f0443adcb931079d13cacab848fb531 Mon Sep 17 00:00:00 2001 From: Dan Beam <251287+danbeam@users.noreply.github.com> Date: Tue, 23 Aug 2022 00:01:36 -0700 Subject: [PATCH] [sinon][ts] convert sinon.SinonStub -> jest.Mock, sinon.SinonSpy -> jest.SpyInstance (#359) * [sinon][ts] convert sinon.SinonStub -> jest.Mock, sinon.SinonSpy -> jest.SpyInstance * move withParser() to test setup code Co-authored-by: Kenneth Skovhus --- src/transformers/sinon.test.ts | 36 ++++++++++++++++++++++++ src/transformers/sinon.ts | 51 +++++++++++++++++++++++++++++----- src/utils/test-helpers.ts | 14 +++++++--- 3 files changed, 90 insertions(+), 11 deletions(-) diff --git a/src/transformers/sinon.test.ts b/src/transformers/sinon.test.ts index 297e73ca..08dfdded 100644 --- a/src/transformers/sinon.test.ts +++ b/src/transformers/sinon.test.ts @@ -690,3 +690,39 @@ describe('mock timers', () => { ) }) }) + +describe('updates types', () => { + it('rewrites types for mocks & stubs', () => { + expectTransformation( + ` + import sinon from 'sinon-sandbox' + + let stub1: sinon.SinonStub + let stub2: sinon.SinonStub = getStub() + let spy1: sinon.SinonSpy + let spy2: sinon.SinonSpy = getSpy() + let doStubThing = (stub: sinon.SinonStub) => stub + let doSpyThing = (spy: sinon.SinonSpy) => spy + + // These should be ignored + let num: number = 5 + let str: string = 'asdf' + let keys = (obj: object) => Object.keys(obj) +`, + ` + let stub1: jest.Mock + let stub2: jest.Mock = getStub() + let spy1: jest.SpyInstance + let spy2: jest.SpyInstance = getSpy() + let doStubThing = (stub: jest.Mock) => stub + let doSpyThing = (spy: jest.SpyInstance) => spy + + // These should be ignored + let num: number = 5 + let str: string = 'asdf' + let keys = (obj: object) => Object.keys(obj) +`, + { parser: 'ts' } + ) + }) +}) diff --git a/src/transformers/sinon.ts b/src/transformers/sinon.ts index 370f1ba2..0f3a8f70 100644 --- a/src/transformers/sinon.ts +++ b/src/transformers/sinon.ts @@ -58,6 +58,8 @@ const SINON_ON_NTH_CALLS = new Set(['onFirstCall', 'onSecondCall', 'onThirdCall' const EXPECT_PREFIXES = new Set(['to']) const isPrefix = (name) => EXPECT_PREFIXES.has(name) +const isTypescript = (parser: string) => parser === 'tsx' || parser === 'ts' + const SINON_CALLS_ARG = new Set([ 'callsArg', 'callsArgOn', @@ -88,11 +90,10 @@ function transformCallsArg(j, ast, parser) { const argName = j.memberExpression(j.identifier('args'), node.arguments[0], true) - const isTypescript = parser === 'tsx' || parser === 'ts' const mockImplementationArg = j.spreadPropertyPattern( j.identifier.from({ name: 'args', - typeAnnotation: isTypescript + typeAnnotation: isTypescript(parser) ? j.typeAnnotation(j.arrayTypeAnnotation(j.anyTypeAnnotation())) : null, }) @@ -552,8 +553,6 @@ function transformStubGetCalls(j: core.JSCodeshift, ast) { .returnsArg */ function transformMock(j: core.JSCodeshift, ast, parser: string) { - const isTypescript = parser === 'tsx' || parser === 'ts' - // stub.withArgs(111).returns('foo') => stub.mockImplementation((...args) => { if (args[0] === '111') return 'foo' }) ast .find(j.CallExpression, { @@ -621,7 +620,7 @@ function transformMock(j: core.JSCodeshift, ast, parser: string) { const mockImplementationArg = j.spreadPropertyPattern( j.identifier.from({ name: 'args', - typeAnnotation: isTypescript + typeAnnotation: isTypescript(parser) ? j.typeAnnotation(j.arrayTypeAnnotation(j.anyTypeAnnotation())) : null, }) @@ -671,7 +670,7 @@ function transformMock(j: core.JSCodeshift, ast, parser: string) { const argsVar = j.identifier.from({ name: 'args', - typeAnnotation: isTypescript + typeAnnotation: isTypescript(parser) ? j.typeAnnotation(j.arrayTypeAnnotation(j.anyTypeAnnotation())) : null, }) @@ -871,6 +870,44 @@ function transformMockTimers(j, ast) { }) } +// let stub: sinon.SinonStub -> let stub: jest.Mock +// let spy: sinon.SinonSpy -> let spy: jest.SpyInstance +function transformTypes(j, ast, parser) { + if (!isTypescript(parser)) return + + ast + .find(j.TSTypeReference, { + typeName: { + left: { + name: 'sinon', + }, + right: { + name: 'SinonStub', + }, + }, + }) + .forEach((np) => { + np.node.typeName.left.name = 'jest' + np.node.typeName.right.name = 'Mock' + }) + + ast + .find(j.TSTypeReference, { + typeName: { + left: { + name: 'sinon', + }, + right: { + name: 'SinonSpy', + }, + }, + }) + .forEach((np) => { + np.node.typeName.left.name = 'jest' + np.node.typeName.right.name = 'SpyInstance' + }) +} + export default function transformer(fileInfo: FileInfo, api: API, options) { const j = api.jscodeshift const ast = j(fileInfo.source) @@ -879,7 +916,6 @@ export default function transformer(fileInfo: FileInfo, api: API, options) { removeDefaultImport(j, ast, 'sinon-sandbox') || removeDefaultImport(j, ast, 'sinon') if (!sinonExpression) { - console.warn(`no sinon for "${fileInfo.path}"`) if (!options.skipImportDetection) { return fileInfo.source } @@ -898,6 +934,7 @@ export default function transformer(fileInfo: FileInfo, api: API, options) { transformCalledWithAssertions(j, ast) transformMatch(j, ast) transformStubGetCalls(j, ast) + transformTypes(j, ast, options.parser) return finale(fileInfo, j, ast, options) } diff --git a/src/utils/test-helpers.ts b/src/utils/test-helpers.ts index 9fab1502..1321c438 100644 --- a/src/utils/test-helpers.ts +++ b/src/utils/test-helpers.ts @@ -1,17 +1,23 @@ import jscodeshift from 'jscodeshift' // simulate the jscodeshift api -export function api(): jscodeshift.API { +export function api(options): jscodeshift.API { + let j = jscodeshift + + if (options.parser) { + j = j.withParser(options.parser) + } + return { - jscodeshift, - j: jscodeshift, + jscodeshift: j, + j, stats: () => {}, report: () => {}, } } export function runPlugin(plugin: jscodeshift.Transform, source: string, options = {}) { - return plugin({ source, path: 'test.js' }, api(), options) + return plugin({ source, path: 'test.js' }, api(options), options) } export function wrapPlugin(plugin: jscodeshift.Transform) {