Giter VIP home page Giter VIP logo

react-javascript-to-typescript-transform's Introduction

React JavaScript to TypeScript Transform

Build Status

Transforms React code written in JavaScript to TypeScript.

๐Ÿ–ฅ Download the VSCode Extension

Features:

  • Proxies PropTypes to React.Component generic type and removes PropTypes
  • Provides state typing for React.Component based on initial state and setState() calls in the component
  • Hoist large interfaces for props and state out of React.Component<P, S> into declared types
  • Convert functional components with PropTypes property to TypeScript and uses propTypes to generate function type declaration

Example

input

class MyComponent extends React.Component {
    static propTypes = {
        prop1: React.PropTypes.string.isRequired,
        prop2: React.PropTypes.number,
    };
    constructor() {
        super();
        this.state = { foo: 1, bar: 'str' };
    }
    render() {
        return (
            <div>
                {this.state.foo}, {this.state.bar}, {this.state.baz}
            </div>
        );
    }
    onClick() {
        this.setState({ baz: 3 });
    }
}

output

type MyComponentProps = {
    prop1: string;
    prop2?: number;
};

type MyComponentState = {
    foo: number;
    bar: string;
    baz: number;
};

class MyComponent extends React.Component<MyComponentProps, MyComponentState> {
    constructor() {
        super();
        this.state = { foo: 1, bar: 'str' };
    }
    render() {
        return (
            <div>
                {this.state.foo}, {this.state.bar}, {this.state.baz}
            </div>
        );
    }
    onClick() {
        this.setState({ baz: 3 });
    }
}

Usage

CLI

npm install -g react-js-to-ts
react-js-to-ts my-react-js-file.js

VSCode plugin

details Download from VSCode Marketplace

Development

Tests

Tests are organized in test folder. For each transform there is a folder that contains folders for each test case. Each test case has input.tsx and output.tsx.

npm test

Watch mode

Pass -w to npm test

npm test -- -w

Only a single test case

Pass -t with transform name and case name space separated to npm test

npm test -- -t "react-js-make-props-and-state-transform propless-stateless"

react-javascript-to-typescript-transform's People

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  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

react-javascript-to-typescript-transform's Issues

Is there a way to silence the "No parser" error message

I currently see this message when I run the CLI: "No parser and no filepath given, using 'babylon' the parser now but this will throw an error in the future. Please specify a parser or a filepath so one can be inferred." Is this expected? Is there a way to quiet it?

create typedefs only?

I have a library of React SFC components. The library is using Proptypes currently. We have both typescript and non-typescript apps which need to use the library. It would be great if we could generate the typedefs only (don't alter the react components...just create the typedefs). Is it possible to call the CLI to just ouput typedefs?

Option to create types without deleting PropTypes

In a large JS/TS (partially typed) codebase, it can be useful to keep the PropTypes around for some runtime type verification, at least for a while.

Obviously there's a risk over time of losing consistency between the PropTypes and the TS types, but I think it can be a useful migration strategy.

Named-function SFCs get mistransformed

I have an SFC written as a named function, so it can be default-exported with the name intact:

export default function PopoverConfirmedButton({...}) { ... }

This gets transformed into

export default const PopoverConfirmedButton: React.SFC<PopoverConfirmedButtonProps> = ({...}) { ... }

which is not a valid ES export statement, so the Prettier step chokes and I get Failed to convert js/components/PopoverConfirmedButton.jsx.

The correct transformation would probably be

const PopoverConfirmedButton: React.SFC<PopoverConfirmedButtonProps> = ({...}) { ... }
export default PopoverConfirmedButton;

arrayOf and shape

First of, thanks for creating this!

Trying to convert a JS file with some advanced propTypes, I don't get the result I expected.

type RouterProps = {
    routes: string;
};

Looking at your conversion code, I see that it's using some regular expressions. To make the converter a lot more stable and extendable, I'd recommend using a proper parser instead. For example, using the acorn-jsx parser we get very nice results:

var acorn = require('acorn-jsx');

var code = "import React from \'react\';\nimport PropTypes from \'prop-types\';\nimport { connect } from \'react-redux\';\nimport { match } from \'.\/match\';\n\nclass Router extends React.Component {\n  constructor() {\n    super();\n    this.state = {\n      component: null,\n      params: {},\n    };\n    this.getNewComponent = this.getNewComponent.bind(this);\n  }\n\n  componentWillMount() {\n    const { routes, router } = this.props;\n    this.getNewComponent(routes, router.pathname);\n  }\n\n  componentWillReceiveProps(nextProps) {\n    const { routes, router } = nextProps;\n    this.getNewComponent(routes, router.pathname);\n  }\n\n  getNewComponent(routes, pathname) {\n    const { route, params } = match(routes, pathname);\n    if (route) {\n      route.load().then((component) => {\n        this.setState({ params, component });\n      });\n    }\n  }\n\n  render() {\n    const { component: Component, params } = this.state;\n    const { queries, hash } = this.props.router;\n    return Component ? <Component params={params} queries={queries} hash={hash} \/> : null;\n  }\n}\n\nRouter.propTypes = {\n  routes: PropTypes.arrayOf(PropTypes.shape({\n    path: PropTypes.string.isRequired,\n    load: PropTypes.func.isRequired,\n    children: PropTypes.array,\n  })).isRequired,\n  router: PropTypes.shape({\n    pathname: PropTypes.string.isRequired,\n    search: PropTypes.string.isRequired,\n    queries: PropTypes.object.isRequired,\n    hash: PropTypes.string.isRequired,\n  }),\n};\n\nconst RouterContainer = connect(({ router }) => ({ router }))(Router);\n\nexport { Router, RouterContainer };\n" 

var ast = acorn.parse(code, {
  sourceType: "module",
  plugins: {
    jsx: { allowNamespaces: false }
  }
});

console.log(ast.body[5])

To try it out paste the code into runkit.

Unable to convert some files

The files https://github.com/kcsry/infotv/blob/68963e5161a811fb0edf10584e7e12466ef470a8/infotv/frontend/src/s/text-slide.jsx and https://github.com/kcsry/infotv/blob/68963e5161a811fb0edf10584e7e12466ef470a8/infotv/frontend/src/s/image-slide.jsx do not convert, unless the propTypes blocks are removed.

The traceback is

TypeError: Cannot read property 'text' of undefined
    at IdentifierObject.TokenOrIdentifierObject.getText (~/node_modules/typescript/lib/typescript.js:100312:31)
    at ~/dist/transforms/react-stateless-function-make-props-transform.js:64:86
    at baseFindIndex (~/node_modules/lodash/lodash.js:823:11)
    at findIndex (~/node_modules/lodash/lodash.js:7275:14)
    at Function.find (~/node_modules/lodash/lodash.js:5098:21)
    at _loop_1 (~/dist/transforms/react-stateless-function-make-props-transform.js:62:31)
    at visitSourceFile (~/dist/transforms/react-stateless-function-make-props-transform.js:73:13)
    at reactStatelessFunctionMakePropsTransform (~/dist/transforms/react-stateless-function-make-props-transform.js:47:27)
    at ~/node_modules/typescript/lib/typescript.js:2888:86
    at reduceLeft (~/node_modules/typescript/lib/typescript.js:2581:30)
    at ~/node_modules/typescript/lib/typescript.js:2888:42
    at Object.map (~/node_modules/typescript/lib/typescript.js:1879:29)
    at Object.transformNodes (~/node_modules/typescript/lib/typescript.js:67699:30)
    at Object.transform (~/node_modules/typescript/lib/typescript.js:102568:25)
    at Object.compile (~/dist/compiler.js:41:21)
    at Object.run (~/dist/index.js:35:23)

Large component test case

This is from a comment in hacker news

//@flow
import PropTypes from 'prop-types';
import React from 'react';

import {
  noop,
  returnTrue,
  charIsNumber,
  escapeRegExp,
  fixLeadingZero,
  splitString,
  limitToScale,
  roundToPrecision,
  omit,
  setCaretPosition,
  splitDecimal
} from './utils';


const propTypes = {
  thousandSeparator: PropTypes.oneOfType([PropTypes.string, PropTypes.oneOf([true])]),
  decimalSeparator: PropTypes.string,
  decimalScale: PropTypes.number,
  fixedDecimalScale: PropTypes.bool,
  displayType: PropTypes.oneOf(['input', 'text']),
  prefix: PropTypes.string,
  suffix: PropTypes.string,
  format: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.func
  ]),
  removeFormatting: PropTypes.func,
  mask: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
  value: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.string
  ]),
  isNumericString: PropTypes.bool,
  customInput: PropTypes.func,
  allowNegative: PropTypes.bool,
  onValueChange: PropTypes.func,
  onKeyDown: PropTypes.func,
  onMouseUp: PropTypes.func,
  onChange: PropTypes.func,
  onFocus: PropTypes.func,
  onBlur: PropTypes.func,
  type: PropTypes.oneOf(['text', 'tel']),
  isAllowed: PropTypes.func,
  renderText: PropTypes.func,
  getInputRef: PropTypes.func
};

const defaultProps = {
  displayType: 'input',
  decimalSeparator: '.',
  fixedDecimalScale: false,
  prefix: '',
  suffix: '',
  allowNegative: true,
  isNumericString: false,
  type: 'text',
  onValueChange: noop,
  onChange: noop,
  onKeyDown: noop,
  onMouseUp: noop,
  onFocus: noop,
  onBlur: noop,
  isAllowed: returnTrue,
  getInputRef: noop
};

class NumberFormat extends React.Component {
  state: {
    value?: string,
    numAsString?: string
  }
  onChange: Function
  onKeyDown: Function
  onMouseUp: Function
  onFocus: Function
  onBlur: Function
  static defaultProps: Object
  constructor(props: Object) {
    super(props);

    //validate props
    this.validateProps();

    const formattedValue = this.formatValueProp();

    this.state = {
      value: formattedValue,
      numAsString: this.removeFormatting(formattedValue)
    }

    this.onChange = this.onChange.bind(this);
    this.onKeyDown = this.onKeyDown.bind(this);
    this.onMouseUp = this.onMouseUp.bind(this);
    this.onFocus = this.onFocus.bind(this);
    this.onBlur = this.onBlur.bind(this);
  }

  componentDidUpdate(prevProps: Object) {
    this.updateValueIfRequired(prevProps);
  }

  updateValueIfRequired(prevProps: Object) {
    const {props, state} = this;

    if(prevProps !== props) {
      //validate props
      this.validateProps();

      const stateValue = state.value;

      const lastNumStr = state.numAsString || '';

      const formattedValue = props.value === undefined ? this.formatNumString(lastNumStr) : this.formatValueProp();

      if (formattedValue !== stateValue) {
        this.setState({
          value : formattedValue,
          numAsString: this.removeFormatting(formattedValue)
        })
      }
    }
  }

  /** Misc methods **/
  getFloatString(num: string = '') {
    const {decimalSeparator} = this.getSeparators();
    const numRegex = this.getNumberRegex(true);

    //remove negation for regex check
    const hasNegation = num[0] === '-';
    if(hasNegation) num = num.replace('-', '');

    num  = (num.match(numRegex) || []).join('').replace(decimalSeparator, '.');

    //remove extra decimals
    const firstDecimalIndex = num.indexOf('.');

    if (firstDecimalIndex !== -1) {
      num = `${num.substring(0, firstDecimalIndex)}.${num.substring(firstDecimalIndex + 1, num.length).replace(new RegExp(escapeRegExp(decimalSeparator), 'g'), '')}`
    }

    //add negation back
    if(hasNegation) num = '-' + num;

    return num;
  }

  //returned regex assumes decimalSeparator is as per prop
  getNumberRegex(g: boolean, ignoreDecimalSeparator?: boolean) {
    const {format, decimalScale} = this.props;
    const {decimalSeparator} = this.getSeparators();
    return new RegExp('\\d' + (decimalSeparator && decimalScale !== 0 && !ignoreDecimalSeparator && !format ? '|' + escapeRegExp(decimalSeparator) : ''), g ? 'g' : undefined);
  }

  getSeparators() {
    const {decimalSeparator} = this.props;
    let {thousandSeparator} = this.props;

    if (thousandSeparator === true) {
      thousandSeparator = ','
    }

    return {
      decimalSeparator,
      thousandSeparator
    }
  }

  getMaskAtIndex (index: number) {
    const {mask = ' '} = this.props;
    if (typeof mask === 'string') {
      return mask;
    }

    return mask[index] || ' ';
  }

  validateProps() {
    const {mask} = this.props;

    //validate decimalSeparator and thousandSeparator
    const {decimalSeparator, thousandSeparator} = this.getSeparators();


    if (decimalSeparator === thousandSeparator) {
      throw new Error(`
          Decimal separator can\'t be same as thousand separator.\n
          thousandSeparator: ${thousandSeparator} (thousandSeparator = {true} is same as thousandSeparator = ",")
          decimalSeparator: ${decimalSeparator} (default value for decimalSeparator is .)
       `);
    }

    //validate mask
    if (mask) {
      const maskAsStr = mask === 'string' ? mask : mask.toString();
      if (maskAsStr.match(/\d/g)) {
        throw new Error(`
          Mask ${mask} should not contain numeric character;
        `)
      }
    }

  }
  /** Misc methods end **/

  /** caret specific methods **/
  setPatchedCaretPosition(el: HTMLInputElement, caretPos: number, currentValue: string) {
    /* setting caret position within timeout of 0ms is required for mobile chrome,
    otherwise browser resets the caret position after we set it
    We are also setting it without timeout so that in normal browser we don't see the flickering */
    setCaretPosition(el, caretPos);
    setTimeout(() => {
      if(el.value === currentValue) setCaretPosition(el, caretPos);
    }, 0);
  }

  /* This keeps the caret within typing area so people can't type in between prefix or suffix */
  correctCaretPosition(value: string, caretPos: number, direction?: string) {
    const {prefix, suffix, format} = this.props;

    //in case of format as number limit between prefix and suffix
    if (!format) {
      const hasNegation = value[0] === '-';
      return Math.min(Math.max(caretPos, prefix.length + (hasNegation ? 1 : 0)), (value.length - suffix.length));
    }

    //in case if custom format method don't do anything
    if (typeof format === 'function') return caretPos;

    /* in case format is string find the closest # position from the caret position */

    //in case the caretPos have input value on it don't do anything
    if (format[caretPos] === '#' && charIsNumber(value[caretPos])) return caretPos;

    //if caretPos is just after input value don't do anything
    if (format[caretPos - 1] === '#' && charIsNumber(value[caretPos - 1])) return caretPos;

    //find the nearest caret position
    const firstHashPosition = format.indexOf('#');
    const lastHashPosition = format.lastIndexOf('#');

    //limit the cursor between the first # position and the last # position
    caretPos = Math.min(Math.max(caretPos, firstHashPosition), lastHashPosition + 1);

    const nextPos = format.substring(caretPos, format.length).indexOf('#');
    let caretLeftBound = caretPos;
    const caretRightBoud = caretPos + (nextPos === -1 ? 0 : nextPos)

    //get the position where the last number is present
    while (caretLeftBound > firstHashPosition && (format[caretLeftBound] !== '#' || !charIsNumber(value[caretLeftBound]))) {
      caretLeftBound -= 1;
    }

    const goToLeft = !charIsNumber(value[caretRightBoud])
    || (direction === 'left' && caretPos !== firstHashPosition)
    || (caretPos - caretLeftBound < caretRightBoud - caretPos);

    return goToLeft ? caretLeftBound + 1 : caretRightBoud;

  }

  getCaretPosition(inputValue: string, formattedValue: string, caretPos: number) {
    const {format} = this.props;
    const stateValue = this.state.value;
    const numRegex = this.getNumberRegex(true);
    const inputNumber = (inputValue.match(numRegex) || []).join('');
    const formattedNumber = (formattedValue.match(numRegex) || []).join('');
    let j, i;

    j = 0;

    for(i=0; i<caretPos; i++){
      const currentInputChar = inputValue[i] || '';
      const currentFormatChar = formattedValue[j] || '';
      //no need to increase new cursor position if formatted value does not have those characters
      //case inputValue = 1a23 and formattedValue =  123
      if(!currentInputChar.match(numRegex) && currentInputChar !== currentFormatChar) continue;

      //When we are striping out leading zeros maintain the new cursor position
      //Case inputValue = 00023 and formattedValue = 23;
      if (currentInputChar === '0' && currentFormatChar.match(numRegex) && currentFormatChar !== '0' && inputNumber.length !== formattedNumber.length) continue;

      //we are not using currentFormatChar because j can change here
      while(currentInputChar !== formattedValue[j] && j < formattedValue.length) j++;
      j++;
    }

    if ((typeof format === 'string' && !stateValue)) {
      //set it to the maximum value so it goes after the last number
      j = formattedValue.length;
    }

    //correct caret position if its outside of editable area
    j = this.correctCaretPosition(formattedValue, j);

    return j;
  }
  /** caret specific methods ends **/


  /** methods to remove formattting **/
  removePrefixAndSuffix(val: string) {
    const {format, prefix, suffix} = this.props;

    //remove prefix and suffix
    if (!format && val) {
      const isNegative = val[0] === '-';

      //remove negation sign
      if (isNegative) val = val.substring(1, val.length);

      //remove prefix
      val = prefix && val.indexOf(prefix) === 0 ? val.substring(prefix.length, val.length) : val;

      //remove suffix
      const suffixLastIndex = val.lastIndexOf(suffix);
      val = suffix && suffixLastIndex !== -1 && suffixLastIndex === val.length - suffix.length ? val.substring(0, suffixLastIndex) : val;

      //add negation sign back
      if (isNegative) val = '-' + val;
    }

    return val;
  }

  removePatternFormatting(val: string) {
    const {format} = this.props;
    const formatArray = format.split('#').filter(str => str !== '');
    let start = 0;
    let numStr = '';

    for (let i=0, ln=formatArray.length; i <= ln; i++) {
      const part = formatArray[i] || '';

      //if i is the last fragment take the index of end of the value
      //For case like +1 (911) 911 91 91 having pattern +1 (###) ### ## ##
      const index = i === ln ? val.length : val.indexOf(part, start);

      /* in any case if we don't find the pattern part in the value assume the val as numeric string
      This will be also in case if user has started typing, in any other case it will not be -1
      unless wrong prop value is provided */
      if (index === -1) {
        numStr = val;
        break;
      } else {
        numStr += val.substring(start, index);
        start = index + part.length;
      }
    }

    return (numStr.match(/\d/g) || []).join('');
  }

  removeFormatting(val: string) {
    const {format, removeFormatting} = this.props;
    if (!val) return val;

    if (!format) {
      val = this.removePrefixAndSuffix(val);
      val = this.getFloatString(val);
    } else if (typeof format === 'string') {
      val = this.removePatternFormatting(val);
    } else if (typeof removeFormatting === 'function') { //condition need to be handled if format method is provide,
      val = removeFormatting(val);
    } else {
      val = (val.match(/\d/g) || []).join('')
    }
    return val;
  }
  /** methods to remove formattting end **/


  /*** format specific methods start ***/
  /**
   * Format when # based string is provided
   * @param  {string} numStr Numeric String
   * @return {string}        formatted Value
   */
  formatWithPattern(numStr: string) {
    const {format} = this.props;
    let hashCount = 0;
    const formattedNumberAry = format.split('');
    for (let i = 0, ln = format.length; i < ln; i++) {
      if (format[i] === '#') {
        formattedNumberAry[i] = numStr[hashCount] || this.getMaskAtIndex(hashCount);
        hashCount += 1;
      }
    }
    return formattedNumberAry.join('');
  }
  /**
   * @param  {string} numStr Numeric string/floatString] It always have decimalSeparator as .
   * @return {string} formatted Value
   */
  formatAsNumber(numStr: string) {
    const {decimalScale, fixedDecimalScale, prefix, suffix, allowNegative} = this.props;
    const {thousandSeparator, decimalSeparator} = this.getSeparators();

    const hasDecimalSeparator = numStr.indexOf('.') !== -1 || (decimalScale && fixedDecimalScale);
    let {beforeDecimal, afterDecimal, addNegation} = splitDecimal(numStr, allowNegative); // eslint-disable-line prefer-const

    //apply decimal precision if its defined
    if (decimalScale !== undefined) afterDecimal = limitToScale(afterDecimal, decimalScale, fixedDecimalScale);

    if(thousandSeparator) {
      beforeDecimal = beforeDecimal.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + thousandSeparator);
    }

    //add prefix and suffix
    if(prefix) beforeDecimal = prefix + beforeDecimal;
    if(suffix) afterDecimal = afterDecimal + suffix;

    //restore negation sign
    if (addNegation) beforeDecimal = '-' + beforeDecimal;

    numStr = beforeDecimal + (hasDecimalSeparator && decimalSeparator ||  '') + afterDecimal;

    return numStr;
  }

  formatNumString(value: string = '') {
    const {format} = this.props;
    let formattedValue = value;

    if (value === '') {
      formattedValue = ''
    } else if (value === '-' && !format) {
      formattedValue = '-';
      value = '';
    } else if (typeof format === 'string') {
      formattedValue = this.formatWithPattern(formattedValue);
    } else if (typeof format === 'function') {
      formattedValue = format(formattedValue);
    } else {
      formattedValue = this.formatAsNumber(formattedValue)
    }

    return formattedValue;
  }

  formatValueProp() {
    const {format, decimalScale, fixedDecimalScale} = this.props;
    let {value, isNumericString} = this.props;

    // if value is not defined return empty string
    if (value === undefined) return '';

    if (typeof value === 'number') {
      value = value.toString();
      isNumericString = true;
    }

    //round the number based on decimalScale
    //format only if non formatted value is provided
    if (isNumericString && !format && typeof decimalScale === 'number') {
      value = roundToPrecision(value, decimalScale, fixedDecimalScale)
    }

    const formattedValue = isNumericString ? this.formatNumString(value) : this.formatInput(value);

    return formattedValue;
  }

  formatNegation(value: string = '') {
    const {allowNegative} = this.props;
    const negationRegex = new RegExp('(-)');
    const doubleNegationRegex = new RegExp('(-)(.)*(-)');

    // Check number has '-' value
    const hasNegation = negationRegex.test(value);

    // Check number has 2 or more '-' values
    const removeNegation = doubleNegationRegex.test(value);

    //remove negation
    value = value.replace(/-/g, '');

    if (hasNegation && !removeNegation && allowNegative) {
      value = '-' + value;
    }

    return value;
  }

  formatInput(value: string = '') {
    const {format} = this.props;

    //format negation only if we are formatting as number
    if (!format) {
      value = this.formatNegation(value);
    }

    //remove formatting from number
    value = this.removeFormatting(value);

    return this.formatNumString(value);
  }

  /*** format specific methods end ***/
  isCharacterAFormat(caretPos: number, value: string) {
    const {format, prefix, suffix, decimalScale, fixedDecimalScale} = this.props;
    const {decimalSeparator} = this.getSeparators();

    //check within format pattern
    if (typeof format === 'string' && format[caretPos] !== '#') return true;

    //check in number format
    if (!format && (caretPos < prefix.length
      || caretPos >= value.length - suffix.length
      || (decimalScale && fixedDecimalScale && value[caretPos] === decimalSeparator))
    ) {
      return true;
    }

    return false;
  }

  checkIfFormatGotDeleted(start: number, end: number, value: string) {
    for (let i = start; i < end; i++) {
      if (this.isCharacterAFormat(i, value)) return true;
    }
    return false;
  }

  /**
   * This will check if any formatting got removed by the delete or backspace and reset the value
   * It will also work as fallback if android chome keyDown handler does not work
   **/
  correctInputValue(caretPos: number, lastValue: string, value: string) {
    const {format, decimalSeparator, allowNegative} = this.props;
    const lastNumStr = this.state.numAsString || '';

    //don't do anyhting if something got added, or if value is empty string (when whole input is cleared)
    if (value.length >= lastValue.length || !value.length) {
      return value;
    }

    const start = caretPos;
    const lastValueParts = splitString(lastValue, caretPos);
    const newValueParts = splitString(value, caretPos);
    const deletedIndex = lastValueParts[1].lastIndexOf(newValueParts[1]);
    const diff = deletedIndex !== -1 ? lastValueParts[1].substring(0, deletedIndex) : '';
    const end = start + diff.length;

    //if format got deleted reset the value to last value
    if (this.checkIfFormatGotDeleted(start, end, lastValue)) {
      value = lastValue;
    }

    //for numbers check if beforeDecimal got deleted and there is nothing after decimal,
    //clear all numbers in such case while keeping the - sign
    if (!format) {
      const numericString = this.removeFormatting(value);
      let {beforeDecimal, afterDecimal, addNegation} = splitDecimal(numericString, allowNegative); // eslint-disable-line prefer-const

      //clear only if something got deleted
      const isBeforeDecimalPoint = caretPos < value.indexOf(decimalSeparator) + 1;
      if (numericString.length < lastNumStr.length && isBeforeDecimalPoint && beforeDecimal === '' && !parseFloat(afterDecimal)) {
        return addNegation ? '-' : '';
      }
    }

    return value;
  }

  onChange(e: SyntheticInputEvent) {
    e.persist();
    const el = e.target;
    let inputValue = el.value;
    const {state, props} = this;
    const {isAllowed} = props;
    const lastValue = state.value || '';

    /*Max of selectionStart and selectionEnd is taken for the patch of pixel and other mobile device caret bug*/
    const currentCaretPosition = Math.max(el.selectionStart, el.selectionEnd);

    inputValue =  this.correctInputValue(currentCaretPosition, lastValue, inputValue);

    let formattedValue = this.formatInput(inputValue) || '';
    const numAsString = this.removeFormatting(formattedValue);

    const valueObj = {
      formattedValue,
      value: numAsString,
      floatValue: parseFloat(numAsString)
    };

    if (!isAllowed(valueObj)) {
      formattedValue = lastValue;
    }

    //set the value imperatively, this is required for IE fix
    el.value = formattedValue;

    //get the caret position
    const caretPos = this.getCaretPosition(inputValue, formattedValue, currentCaretPosition);

    //set caret position
    this.setPatchedCaretPosition(el, caretPos, formattedValue);

    //change the state
    if (formattedValue !== lastValue) {
      this.setState({value : formattedValue, numAsString}, () => {
        props.onValueChange(valueObj);
        props.onChange(e);
      });
    } else {
      props.onChange(e);
    }
  }

  onBlur(e: SyntheticInputEvent) {
    const {props, state} = this;
    const {format, onBlur} = props;
    let {numAsString} = state;
    const lastValue = state.value;
    if (!format) {
      numAsString = fixLeadingZero(numAsString);
      const formattedValue = this.formatNumString(numAsString);
      const valueObj = {
        formattedValue,
        value: numAsString,
        floatValue: parseFloat(numAsString)
      };

      //change the state
      if (formattedValue !== lastValue) {
        // the event needs to be persisted because its properties can be accessed in an asynchronous way
        e.persist();
        this.setState({value : formattedValue, numAsString}, () => {
          props.onValueChange(valueObj);
          onBlur(e);
        });
        return;
      }
    }
    onBlur(e);
  }

  onKeyDown(e: SyntheticKeyboardInputEvent) {
    const el = e.target;
    const {key} = e;
    const {selectionEnd, value} = el;
    const {selectionStart} = el;
    let expectedCaretPosition;
    const {decimalScale, fixedDecimalScale, prefix, suffix, format, onKeyDown} = this.props;
    const ignoreDecimalSeparator = decimalScale !== undefined && fixedDecimalScale;
    const numRegex = this.getNumberRegex(false, ignoreDecimalSeparator);
    const negativeRegex = new RegExp('-');
    const isPatternFormat = typeof format === 'string';

    //Handle backspace and delete against non numerical/decimal characters or arrow keys
    if (key === 'ArrowLeft' || key === 'Backspace') {
      expectedCaretPosition = selectionStart - 1;
    } else if (key === 'ArrowRight') {
      expectedCaretPosition = selectionStart + 1;
    } else if (key === 'Delete') {
      expectedCaretPosition = selectionStart;
    }

    //if expectedCaretPosition is not set it means we don't want to Handle keyDown
    //also if multiple characters are selected don't handle
    if (expectedCaretPosition === undefined || selectionStart !== selectionEnd) {
      onKeyDown(e);
      return;
    }

    let newCaretPosition = expectedCaretPosition;
    const leftBound = isPatternFormat ? format.indexOf('#') : prefix.length;
    const rightBound = isPatternFormat ? format.lastIndexOf('#') + 1 : value.length - suffix.length;

    if (key === 'ArrowLeft' || key === 'ArrowRight') {
      const direction = key === 'ArrowLeft' ? 'left' : 'right';
      newCaretPosition = this.correctCaretPosition(value, expectedCaretPosition, direction);
    } else if (key === 'Delete' && !numRegex.test(value[expectedCaretPosition]) && !negativeRegex.test(value[expectedCaretPosition])) {
      while (!numRegex.test(value[newCaretPosition]) && newCaretPosition < rightBound) newCaretPosition++;
    } else if (key === 'Backspace' && !numRegex.test(value[expectedCaretPosition]) && !negativeRegex.test(value[expectedCaretPosition])) {
      while (!numRegex.test(value[newCaretPosition - 1]) && newCaretPosition > leftBound){ newCaretPosition--; }
      newCaretPosition = this.correctCaretPosition(value, newCaretPosition, 'left');
    }


    if (newCaretPosition !== expectedCaretPosition || expectedCaretPosition < leftBound || expectedCaretPosition > rightBound) {
      e.preventDefault();
      this.setPatchedCaretPosition(el, newCaretPosition, value);
    }

    /* NOTE: this is just required for unit test as we need to get the newCaretPosition,
            Remove this when you find different solution */
    if (e.isUnitTestRun) {
      this.setPatchedCaretPosition(el, newCaretPosition, value);
    }


    this.props.onKeyDown(e);

  }

  /** required to handle the caret position when click anywhere within the input **/
  onMouseUp(e: SyntheticMouseInputEvent) {
    const el = e.target;
    const {selectionStart, selectionEnd, value} = el;

    if (selectionStart === selectionEnd) {
      const caretPostion = this.correctCaretPosition(value, selectionStart);
      if (caretPostion !== selectionStart) {
        this.setPatchedCaretPosition(el, caretPostion, value);
      }
    }

    this.props.onMouseUp(e);
  }

  onFocus(e: SyntheticInputEvent) {
    // Workaround Chrome and Safari bug https://bugs.chromium.org/p/chromium/issues/detail?id=779328
    // (onFocus event target selectionStart is always 0 before setTimeout)
    e.persist()
    setTimeout(() => {
      const el = e.target;
      const {selectionStart, value} = el;

      const caretPosition = this.correctCaretPosition(value, selectionStart);
      if (caretPosition !== selectionStart) {
        this.setPatchedCaretPosition(el, caretPosition, value);
      }

      this.props.onFocus(e);
    }, 0);
  }

  render() {
    const {type, displayType, customInput, renderText, getInputRef} = this.props;
    const {value} = this.state;

    const otherProps = omit(this.props, propTypes);

    const inputProps = Object.assign({}, otherProps, {
      type,
      value,
      onChange: this.onChange,
      onKeyDown: this.onKeyDown,
      onMouseUp: this.onMouseUp,
      onFocus: this.onFocus,
      onBlur: this.onBlur
    })

    if( displayType === 'text'){
      return renderText ? (renderText(value) || null) : <span {...otherProps} ref={getInputRef}>{value}</span>;
    }

    else if (customInput) {
      const CustomInput = customInput;
      return (
        <CustomInput
          {...inputProps}
        />
      )
    }

    return (
      <input
        {...inputProps}
        ref = {getInputRef}
      />
    )
  }
}

NumberFormat.propTypes = propTypes;
NumberFormat.defaultProps = defaultProps;

module.exports =  NumberFormat;

Cannot read property 'kind' of undefined

When I run

react-js-to-ts my-react-js-file.js

I got this error

conemu64_2018-12-02_19-59-18

Below is my code

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

import {validateInput} from "../../Helper/Validation";

import Spinner from "../ACCAnimation/Spinner.jsx";

const ACCButton = props => {
  let isDisabled;
  if (props.validationArr) {
    const errors = validateInput(props.validationArr);
    isDisabled = props.disabled
      ? true
      : Object
        .keys(errors)
        .some(x => errors[x]);
  }

  const className = props.btnBlocked
    ? "btn btn-primary btn-block"
    : "btn btn-primary";

  if (props.loading) {
    return <Spinner/>;
  } else {
    return (
      <button
        className={className}
        type="submit"
        disabled={isDisabled || props.disabled}
        onClick={props.onClick}>
        {props.label}
      </button>
    );
  }
};

ACCButton.propTypes = {
  loading: PropTypes.bool,
  label: PropTypes.string,
  validationArr: PropTypes.array,
  disabled: PropTypes.bool,
  onClick: PropTypes.func,
  btnBlocked: PropTypes.string
};

export default ACCButton;

Bad code formatting

It's currently reformating the files with a different config than the one my project uses, and messing with it. For example, it's removing new lines from every code but I don't think that's prettier, prettier respects new lines.

Any way to disable formatting?
And also pass the prettier config path for it to use.
Thanks.

separate transformers from core

hi,

first of all, nice project!
i need the ability to run my own set of trasformers on a js codebase. ( not react specific )
do you think that in future you will publish the core of this repo as a standalone project and the react trasformers as a dedicated plugin?

thanks.

Failed to parse JS file

Steps to reproduce:
1)Installed package and tried either CLI and VSCode version in windows 10 pro
2)Tried adding typescript and tslint globally.
3)Tested with and without tsconfig file

Expected output:
Convert the react file and produce a tsx file with the appropriate changes

Actual output:

Failed to convert ./summary.js
TypeError: Cannot read property 'kind' of undefined
    at Object.isSourceFile (C:\Users\cyber\AppData\Roaming\npm\node_modules\react-js-to-ts\node_modules\typescript\lib\typescript.js:11833:20)
    at Object.printNode (C:\Users\cyber\AppData\Roaming\npm\node_modules\react-js-to-ts\node_modules\typescript\lib\typescript.js:68607:40)
    at Object.compile (C:\Users\cyber\AppData\Roaming\npm\node_modules\react-js-to-ts\dist\compiler.js:39:27)
    at Object.run (C:\Users\cyber\AppData\Roaming\npm\node_modules\react-js-to-ts\dist\index.js:34:23)
    at Command.<anonymous> (C:\Users\cyber\AppData\Roaming\npm\node_modules\react-js-to-ts\dist\cli.js:46:29)
    at Command.listener (C:\Users\cyber\AppData\Roaming\npm\node_modules\react-js-to-ts\node_modules\commander\index.js:315:8)
    at emitOne (events.js:115:13)
    at Command.emit (events.js:210:7)
    at Command.parseArgs (C:\Users\cyber\AppData\Roaming\npm\node_modules\react-js-to-ts\node_modules\commander\index.js:655:12)
    at Command.parse (C:\Users\cyber\AppData\Roaming\npm\node_modules\react-js-to-ts\node_modules\commander\index.js:475:21)
    at Object.<anonymous> (C:\Users\cyber\AppData\Roaming\npm\node_modules\react-js-to-ts\dist\cli.js:55:9)
    at Module._compile (module.js:573:30)
    at Object.Module._extensions..js (module.js:584:10)
    at Module.load (module.js:507:32)
    at tryModuleLoad (module.js:470:12)
    at Function.Module._load (module.js:462:3)

The file is created as .tsx but no changes to the file are made.

Separate imports and components

There should be an empty line between import groups and actual component code.

Imports should be grouped by:

  • lib imports like React and moment
  • Relative other component imports
  • Absolute other component imports
  • CSS and image imports

There should be an empty line after type declarations

In a multi-component file, there should be an empty line between components

There should be an empty line above export statements

bug in vscode extension

extension version 1.10.0

Happens when I activate the extension.

/C:/Program Files/Microsoft VS Code/resources/app/out/vs/workbench/workbench.main.js:2379 Error: Cannot read property 'kind' of undefined
	at Object.isSourceFile (c:\Users\work\.vscode\extensions\mohsen1.react-javascript-to-typescript-transform-vscode-1.10.0\node_modules\typescript\lib\typescript.js:11833:21)
	at Object.printNode (c:\Users\work\.vscode\extensions\mohsen1.react-javascript-to-typescript-transform-vscode-1.10.0\node_modules\typescript\lib\typescript.js:68607:40)
	at Object.compile (c:\Users\work\.vscode\extensions\mohsen1.react-javascript-to-typescript-transform-vscode-1.10.0\node_modules\react-js-to-ts\dist\compiler.js:39:27)
	at Object.run (c:\Users\work\.vscode\extensions\mohsen1.react-javascript-to-typescript-transform-vscode-1.10.0\node_modules\react-js-to-ts\dist\index.js:34:23)
	at c:\Users\work\.vscode\extensions\mohsen1.react-javascript-to-typescript-transform-vscode-1.10.0\out\src\extension.js:24:41
	at Generator.next (<anonymous>)
	at fulfilled (c:\Users\work\.vscode\extensions\mohsen1.react-javascript-to-typescript-transform-vscode-1.10.0\out\src\extension.js:4:58)

My VScode details:

Version: 1.35.1 (system setup)
Commit: c7d83e57cd18f18026a8162d042843bda1bcf21f
Date: 2019-06-12T14:30:02.622Z
Electron: 3.1.8
Chrome: 66.0.3359.181
Node.js: 10.2.0
V8: 6.6.346.32
OS: Windows_NT x64 10.0.17134

export types as well

Hey,
Is it possible to export the generated types as well ? currently only the component is exported.
So in the following example I would like to also export SomeComponentProps.

import React, { Component } from "react";
import { View, Text } from "react-native";
type SomeComponentProps = {
  label?: string,
  color?: string
};
class SomeComponent extends Component<SomeComponentProps, {}> {
  state = {};
  render() {
    return (
      <View>
        <Text>SomeComponent</Text>
      </View>
    );
  }
}
export default SomeComponent;

How to transform react import

In JavaScript, we often use default import & named import:

import React, { Component } from 'react';

But TypeScript prefer to use namespace import:

import * as React from 'react';

First, we should replace the import with namespace import, then we should replace Component with React.Component. but we should also do this for all other possible named import:

React.Children
React.Component
React.PureComponent
React.Fragment
React.createElement
React.cloneElement
React.createFactory
React.isValidElement

Any idea to do this transform? @mohsen1

Tslint integration?

It would be nice if Tslint was also integrated somehow, since Prettier is.

Right now my workflow is running the CLI followed by tslint --fix (well, integrated into PyCharm, but that's beside the point) on the transformed files, followed by manual adjustments.

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.