Giter VIP home page Giter VIP logo

ratchet's Introduction

Ratchet

Test GitHub deployments

Codemod to convert React PropTypes to TypeScript types.

Key Features

  • Supports function and class components
  • Supports static propTypes declarations on class components
  • Supports forwardRefs
  • Supports files with multiple components
  • Copies JSDoc comments to the generated TypeScript types
  • Option to remove or preserve PropTypes after converting to TS

Usage

Thanks to my friends at Codemod, you can easily run Ratchet on your project using the Codemod VS Code extension:

Run in Codemod.com

To learn more about using the Codemod platform, read the docs here. If you encounter any issues, please reach out to my good friends at Codemod.

Or run the following command with a file glob that matches the files you want to convert.

npx jscodeshift -t https://go.mskelton.dev/ratchet.ts GLOB

# Example
npx jscodeshift -t https://go.mskelton.dev/ratchet.ts src/**/*.{js,jsx}

Try it Online!

Additionally, you can use Ratchet online at ratchet.mskelton.dev! Simply paste your input on the left and instantly see the output on the right!

Screenshot

Example: Function Component

Input:

// Input
import PropTypes from "prop-types"
import React from "react"

export function MyComponent(props) {
  return <span />
}

MyComponent.propTypes = {
  bar: PropTypes.string.isRequired,
  foo: PropTypes.number,
}

Output:

import React from "react"

interface MyComponentProps {
  bar: string
  foo?: number
}

export function MyComponent(props: MyComponentProps) {
  return <span />
}

Options

--preserve-prop-types

Preserves prop types after converting to TS. There are two available modes: all and unconverted.

--preserve-prop-types=all

CLI alias: --preserve-prop-types

This option will preserve all PropTypes. This is useful for component libraries where you support both TypeScript declarations and PropTypes.

Input:

import PropTypes from "prop-types"
import React from "react"

export function MyComponent(props) {
  return <span />
}

MyComponent.propTypes = {
  foo: PropTypes.number,
}

Output:

import PropTypes from "prop-types"
import React from "react"

interface MyComponentProps {
  foo?: number
}

export function MyComponent(props: MyComponentProps) {
  return <span />
}

MyComponent.propTypes = {
  foo: PropTypes.number,
}

--preserve-prop-types=unconverted

This option will preserve prop types which could not be fully converted. For example, spread expressions are not converted, and custom validators are converted to unknown. This option is useful to preserve these expressions so you can manually review and convert to their TypeScript equivalent.

Input:

import PropTypes from "prop-types"
import React from "react"

export function MyComponent(props) {
  return <span />
}

MyComponent.propTypes = {
  ...OtherComponent.propTypes,
  foo: PropTypes.number,
  bar(props, propName, componentName) {
    return new Error("Invalid prop")
  },
}

Output:

import PropTypes from "prop-types"
import React from "react"

interface MyComponentProps {
  foo?: number
  bar: unknown
}

export function MyComponent(props: MyComponentProps) {
  return <span />
}

MyComponent.propTypes = {
  ...OtherComponent.propTypes,
  bar(props, propName, componentName) {
    return new Error("Invalid prop")
  },
}

--declaration-style

Allow to choose between interfaces & type aliases. Default is interface.

--declaration-style=type

Create type alias instead of interface.

Input:

// Input
import PropTypes from "prop-types"
import React from "react"

export function MyComponent(props) {
  return <span />
}

MyComponent.propTypes = {
  bar: PropTypes.string.isRequired,
  foo: PropTypes.number,
}

Output:

import React from "react"

type MyComponentProps = {
  bar: string
  foo?: number
}

export function MyComponent(props: MyComponentProps) {
  return <span />
}

ratchet's People

Contributors

bnaya avatar dependabot[bot] avatar krishnaglick avatar marcagba avatar mohab-sameh avatar mskelton avatar tuchandra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

ratchet's Issues

Interested in `forwardRef` support?

This library worked for all of our class and function components, but I had to fork it to get forwardRef working - would you be interested in a PR that both helps with finding the componentName and placing the declaration?

I'm referring to this pattern:

interface ComponentProps {}

const Component = React.forwardRef<React.HTMLSomethingElement, ComponentProps>( () => { })

the tricky part is we can't easily determine for the user what type of element to use for the first type arg

feature proposal: choose between interface & type alias

First thing first: Thank you for your awesome project ! ๐Ÿ™Œ

I forked it because I needed to convert prop-types to type aliases instead of interfaces.

Since I have a working implementation I felt like I would contribute back to your repo.

PR coming soon!

Error when running Ratchet

I'm trying to run the Ratchet codemod on my local project, but I'm running into an error:

npx jscodeshift -t https://mskelton.dev/ratchet.ts src/components/shared/modal.jsx
Processing 1 files...
Spawning 1 workers...
Sending 1 files to free worker...
/private/var/folders/mt/4fyz36kj69l0j5fffrwlsvdh0000gn/T/jscodeshift2022729-65619-1oipzsm.fzej:1
import type { NodePath } from "ast-types/lib/node-path"
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at compileFunction (<anonymous>)
    at wrapSafe (internal/modules/cjs/loader.js:1001:16)
    at Module._compile (internal/modules/cjs/loader.js:1049:27)
    at Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
    at Object.newLoader [as .js] (/Users/landon/Development/chessercise/chessercise/node_modules/pirates/lib/index.js:141:7)
    at Module.load (internal/modules/cjs/loader.js:950:32)
    at Function.Module._load (internal/modules/cjs/loader.js:790:12)
    at Module.require (internal/modules/cjs/loader.js:974:19)
    at require (internal/modules/cjs/helpers.js:93:18)
    at setup (/Users/landon/Development/chessercise/chessercise/node_modules/jscodeshift/src/Worker.js:80:18)
All done.
Results:
0 errors
0 unmodified
0 skipped
0 ok
Time elapsed: 1.651seconds

Any ideas?

Output types to .d.ts file

Currently the transform function puts the types directly into the modified file. How feasible would it be to emit them to a named .d.ts file next to the altered file?
I'm working w/ jsx files that I cannot convert to typescript files at the moment and would love to just have the types living next to them!

Online tool doesn't output types

input:

import * as React from 'react';
import PropTypes from 'prop-types';
import styles from './Button.module.scss';

const Button = ({ buttonSize, buttonClass, disabled, onClick, type, children, dataTestid }) => {
  const fullButtonClass = `${styles.button} ${styles[buttonSize.className]} ${styles[buttonClass.className]}`;

  return (
    <button
      className={fullButtonClass}
      /* eslint-disable-next-line react/button-has-type */
      type={type}
      onClick={onClick}
      disabled={disabled}
      data-testid={dataTestid}
    >
      {children}
    </button>
  );
};

Button.defaultProps = {
  buttonSize: BUTTON_SIZES.MEDIUM,
  buttonClass: BUTTON_CLASSES.DEFAULT,
  dataTestid: 'button',
  disabled: false,
  type: 'submit',
  children: undefined,
  onClick: () => {},
};

Button.propTypes = {
  buttonSize: PropTypes.object,
  buttonClass: PropTypes.object,
  children: PropTypes.any,
  dataTestid: PropTypes.string,
  disabled: PropTypes.bool,
  onClick: PropTypes.func,
  type: PropTypes.string,
};

export default Button;

output:

import * as React from 'react';
import styles from './Button.module.scss';

const Button = ({ buttonSize, buttonClass, disabled, onClick, type, children, dataTestid }) => {
  const fullButtonClass = `${styles.button} ${styles[buttonSize.className]} ${styles[buttonClass.className]}`;

  return (
    <button
      className={fullButtonClass}
      /* eslint-disable-next-line react/button-has-type */
      type={type}
      onClick={onClick}
      disabled={disabled}
      data-testid={dataTestid}
    >
      {children}
    </button>
  );
};

Button.defaultProps = {
  buttonSize: BUTTON_SIZES.MEDIUM,
  buttonClass: BUTTON_CLASSES.DEFAULT,
  dataTestid: 'button',
  disabled: false,
  type: 'submit',
  children: undefined,
  onClick: () => {},
};

export default Button;

only difference is that it removes the Button.propTypes

Custom validator function

  • Custom validation function
  • PropTypes.arrayOf with custom function
  • PropTypes.objectOf with custom function

Error with PropTypes.oneOf converting args to StringLiteral

Background

I came across this tool while looking for ways to convert the PropTypes from plotly/dash into TypeScript interfaces. It worked really well (thank you!), with a few errors that I think I have narrowed down to the usage of oneOf below.

To reproduce

I originally ran into this using the jscodeshift CLI, but to narrow down the source I went to the web tool (which is really slick, I'm so impressed!). This should be a minimal reproducible example:

import PropTypes from 'prop-types';
import { Component } from 'react';

export default class MyComponent extends Component {}

MyComponent.propTypes = {
    works: PropTypes.bool.isRequired,
//    fails: PropTypes.oneOf([4, 5]),
};

The 'works' prop, well, works fine; but uncommenting the 'fails' prop generates an error in the browser console:

Transform error. Error: 4 does not match field "value": string of type StringLiteral

Possible cause

Unfortunately, I don't have the JS know-how to understand the minified error message, but a quick search for StringLiteral points me to this block. It seems that the transform encountering PropTypes.oneOf converts the members to strings, which might mean oneOf doesn't work with non-strings.

I'm able to create a failing test by adding to __testfixtures__/complex-props-input.js โ€”

MyComponent.propTypes = {
  // ...
  optionalEnumNumbers: PropTypes.oneOf([1, 2, 3]),
  // ...
}

(This is a really impressive tool, by the way; thank you for sharing this in the open!)

Issues with parsing destructured import

When using both the script and online tool, if I use a file with destructured prop-types the types that get generated are unknown.

For example:

import { bool, string } from 'prop-types'
...
Component.propTypes = { isDisabled: bool, name: string }

will generate

interface ComponentProps {
  isDisabled?: unknown;
  name?: unknown;
}

PropTypes defined on a variable aren't processed

Example code:

import PropTypes, {InferProps} from 'prop-types';
import Task from './task';
import Row from '../row';

const TaskRow = (props: InferProps<typeof propTypes>) => (
  <Row
    contentStyle={{
      paddingLeft: props.level * 10,
    }}
    onPress={props.onPress}
  >
    <Task {...props} />
  </Row>
);

const propTypes = {
  level: PropTypes.number.isRequired,
  onPress: PropTypes.func.isRequired,
};

TaskRow.propTypes = propTypes;
TaskRow.defaultProps = {
  level: 1,
};

export default TaskRow;

Improve preserve-prop-types option

preserve-prop-types should accept an arg of all or unconverted. unconverted should only preserve the prop types which could not be converted. --preserve-prop-types should be an alias for --preserve-prop-types=all

Transform error. Error: undefined does not match field "name": string of type Identifier

Navigate to https://mskelton.dev/ratchet

Enter the following -

import PropTypes from "prop-types"
import React from "react"

export function MyComponent(props) {
  return <span />
}

MyComponent.propTypes = {
  bar: PropTypes.string.isRequired,
  'dashed-prop': PropTypes.number,
}

Error:

Transform error. Error: undefined does not match field "name": string of type Identifier
    at n (main.ab6196fd287e15dd275b.js:2:101878)
    at main.ab6196fd287e15dd275b.js:2:102196
    at Array.forEach (<anonymous>)
    at Function.i [as identifier] (main.ab6196fd287e15dd275b.js:2:102169)
    at V (main.ab6196fd287e15dd275b.js:2:2677597)
    at Array.map (<anonymous>)
    at te (main.ab6196fd287e15dd275b.js:2:2680210)
    at e.<anonymous> (main.ab6196fd287e15dd275b.js:2:2680262)
    at main.ab6196fd287e15dd275b.js:2:2537682
    at Array.forEach (<anonymous>)
a @ main.ab6196fd287e15dd275b.js:2
(anonymous) @ main.ab6196fd287e15dd275b.js:2
(anonymous) @ main.ab6196fd287e15dd275b.js:2
(anonymous) @ main.ab6196fd287e15dd275b.js:2
(anonymous) @ main.ab6196fd287e15dd275b.js:2
setTimeout (async)
(anonymous) @ main.ab6196fd287e15dd275b.js:2
(anonymous) @ main.ab6196fd287e15dd275b.js:2
(anonymous) @ main.ab6196fd287e15dd275b.js:2
(anonymous) @ main.ab6196fd287e15dd275b.js:2
Vn.t @ main.ab6196fd287e15dd275b.js:2
Vn @ main.ab6196fd287e15dd275b.js:2
(anonymous) @ main.ab6196fd287e15dd275b.js:2

Transformation error (Cannot read properties of undefined (reading 'length'))

I'm trying to convert part of a JS codebase to TS and want to automate switching from propTypes to TypeScript.

I've ran the script and it did change some of the files successfully, yet it then errored and stopped:

 ERR PATH-TO-MY-COMPONENT.js Transformation error (Cannot read properties of undefined (reading 'length'))
TypeError: Cannot read properties of undefined (reading 'length')
    at NodePath.each 
   [...]

It throws the same error for almost all components and stops without processing them.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.