diff --git a/src/generate-contents-with-interface.ts b/src/generate-contents-with-interface.ts index 2c300ca9..33e90916 100644 --- a/src/generate-contents-with-interface.ts +++ b/src/generate-contents-with-interface.ts @@ -18,6 +18,9 @@ import type { FunctionDeclaration, FunctionExpression, Identifier, + NumericLiteral, + StringLiteral, + BooleanLiteral, VariableDeclaration, } from "@babel/types"; import * as t from "@babel/types"; @@ -96,13 +99,35 @@ interface CustomCommand { parameters: Identifier[]; } +const inferTypeAnnotationFromDefault = ( + right: AssignmentPattern["right"], +): Identifier["typeAnnotation"] => { + if (t.isNumericLiteral(right as NumericLiteral)) { + return t.tsTypeAnnotation(t.tsNumberKeyword()); + } + if (t.isStringLiteral(right as StringLiteral)) { + return t.tsTypeAnnotation(t.tsStringKeyword()); + } + if (t.isBooleanLiteral(right as BooleanLiteral)) { + return t.tsTypeAnnotation(t.tsBooleanKeyword()); + } + return null; +}; + const generateOptionalParameterFromInitializer = ({ left, -}: AssignmentPattern): Identifier => ({ - ...(left as Identifier), - type: "Identifier", - optional: true, -}); + right, +}: AssignmentPattern): Identifier => { + const id = left as Identifier; + const typeAnnotation = + id.typeAnnotation ?? inferTypeAnnotationFromDefault(right); + return { + ...id, + type: "Identifier", + optional: true, + typeAnnotation, + }; +}; const generateInterface = ( customCommands: CustomCommand[], diff --git a/test/generate-contents-with-interface.test.ts b/test/generate-contents-with-interface.test.ts index 362d610d..29482335 100644 --- a/test/generate-contents-with-interface.test.ts +++ b/test/generate-contents-with-interface.test.ts @@ -272,6 +272,78 @@ export const objectDestructureExample = ({ input1, input2 }: { input1: string; i ).rejects.toThrow(); }); + it("should infer type from boolean default argument", async () => { + readFileSyncMock.mockReturnValue(`export function example(input1 = true) { + cy.log('test'); +} +`); + const result = await generateContentsWithInterface( + filePath, + prettierConfig, + ); + expect(result).toEqual(`export function example(input1 = true) { + cy.log('test'); +} + +/** Generated by cypress-codegen **/ +declare global { + namespace Cypress { + interface Chainable { + example(input1?: boolean): Chainable; + } + } +} +`); + }); + + it("should infer type from string default argument", async () => { + readFileSyncMock.mockReturnValue(`export function example(input1 = 'hello') { + cy.log('test'); +} +`); + const result = await generateContentsWithInterface( + filePath, + prettierConfig, + ); + expect(result).toEqual(`export function example(input1 = 'hello') { + cy.log('test'); +} + +/** Generated by cypress-codegen **/ +declare global { + namespace Cypress { + interface Chainable { + example(input1?: string): Chainable; + } + } +} +`); + }); + + it("should infer type from numeric default argument", async () => { + readFileSyncMock.mockReturnValue(`export function example(input1 = 123) { + cy.log('test'); +} +`); + const result = await generateContentsWithInterface( + filePath, + prettierConfig, + ); + expect(result).toEqual(`export function example(input1 = 123) { + cy.log('test'); +} + +/** Generated by cypress-codegen **/ +declare global { + namespace Cypress { + interface Chainable { + example(input1?: number): Chainable; + } + } +} +`); + }); + it("should handle file with only exports", async () => { readFileSyncMock.mockReturnValue(`export * from './some-file'; export * from './some-other-file';