facebook / jscodeshift

A JavaScript codemod toolkit.

Home Page:https://jscodeshift.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Transformation error (Using the export keyword between a decorator and a class is not allowed. Please use `export @dec class` instead.

saravanakumar-apolloio opened this issue · comments

While doing the codemod via jscodeshift API, I get the error as below.

Screenshot 2022-04-21 at 2 51 05 PM

Transformation error (Using the export keyword between a decorator and a class is not allowed. Please use export @dec class instead.

My React Code

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import _ from 'lodash';
import {Button} from 'common/components';
import Link from 'common/components/ui/Link';
import showDialog from 'app/lib/showDialog';
import PersonaEditDialogContainer from 'app/components/personas/PersonaEditDialogContainer';

function mapStateToProps(state, ownProps) {
  return {
    personas: _.values(state.entities.personas).filter(
      (p) => ownProps.personaIds.indexOf(p.id) != -1,
    ),
  };
}
@connect(
  mapStateToProps,
  {},
)
export default class PersonaLinks extends Component {
  static propTypes = {
    personaIds: PropTypes.array.isRequired,
    handlePersonaUpdate: PropTypes.func,
  };

  handlePersonaClick = (persona) => {
    showDialog(this.context, PersonaEditDialogContainer, {
      mode: 'edit',
      persona,
      updatePersonaCallback: this.props.handlePersonaUpdate,
    });
  };

  render() {
    const { personas } = this.props;
    if (personas.length == 0) {
      return null;
    } else {
      return (
        <span>
          {personas.map((p, i) => (
            <span>
              <Button iconName="minus"/>
              <Link onClick={this.handlePersonaClick.bind(this, p)}>{p.name}</Link>
              {i < personas.length - 2 && ', '}
              {i == personas.length - 2 && ' and '}
            </span>
          ))}
        </span>
      );
    }
  }
}

My TransformFile

/* eslint-disable no-param-reassign */
const addImports = require('jscodeshift-add-imports');

module.exports.parser = 'babel';

module.exports = function transformer(file, api) {
  const j = api.jscodeshift;
  const { statement } = j.template;

  const rootSource = j(file.source);

  /**
   * Remove import statements if Button is the only specifier in the import statements
   */
  rootSource.find(j.ImportDeclaration).forEach((path) => {
    if (path.value.specifiers.length === 1 && path.value.specifiers[0].local.name === 'Button') {
      j(path).remove();
    }
  });

  /**
   * Remove old button references from import statements
   */
  rootSource.find(j.ImportDeclaration).forEach((path) => {
    const updatedSpecifiers = [];
    const specifiers = path.value.specifiers;
    specifiers.forEach((specifier) => {
      if (specifier.local.name !== 'Button') {
        updatedSpecifiers.push(specifier);
      }
      path.value.specifiers = updatedSpecifiers;
    });
  });

  /**
   * Adds the NewButton import statement.
   * * Uses `jscodeshift-add-imports` library for the same.
   */
  addImports(rootSource, [
    statement`import NewButton from 'common/components/design-system/Button';`,
  ]);

  // Modifiy the component name in the usage
  rootSource.find(j.JSXIdentifier, { name: 'Button' }).forEach((path) => {
    path.value.name = 'NewButton';
  });

  //Modify the props for the Button component
  rootSource.find(j.JSXIdentifier, { name: 'NewButton' }).forEach((path) => {
    const attributes = path.parentPath.value.attributes;
    attributes &&
      attributes.forEach((apath) => {
        if (apath.name && apath.name.name === 'iconName') {
          apath.name.name = 'icon';
        }
      });
  });

  return rootSource.toSource();
};

I'll look into this when I get back home. First off, though, what happens if you replace

module.exports.parser = 'babel';

with

module.exports.parser = 'babylon';

It didn't workout though I updated it to babylon or setting parser as babylon via options as well.

@ElonVolo It is due to the decoratorsBeforeExport: false in both babel and babylon parser. While setting it to true it allows the decorators.

For now I copied the entire options and modified decoratorsBeforeExport: false and passes as a parser config via options. I see this will be a work-around for me now. Please find the updated options as below.

const options = {
  dry: true,
  print: true,
  verbose: 1,
  parser: 'babylon',
  ignorePattern: '*.scss',
  parserConfig: {
    sourceType: 'module',
    allowImportExportEverywhere: true,
    allowReturnOutsideFunction: true,
    startLine: 1,
    tokens: true,
    plugins: [
      ['flow', { all: true }],
      'flowComments',
      'jsx',

      'asyncGenerators',
      'bigInt',
      'classProperties',
      'classPrivateProperties',
      'classPrivateMethods',
      ['decorators', { decoratorsBeforeExport: true }],
      'doExpressions',
      'dynamicImport',
      'exportDefaultFrom',
      'exportNamespaceFrom',
      'functionBind',
      'functionSent',
      'importMeta',
      'logicalAssignment',
      'nullishCoalescingOperator',
      'numericSeparator',
      'objectRestSpread',
      'optionalCatchBinding',
      'optionalChaining',
      ['pipelineOperator', { proposal: 'minimal' }],
      'throwExpressions',
    ],
  },
};

Please do let me know if any other actual way workout for this.

@saravanakumar-apolloio Good find (yeah, it was totally duh and I missed it. Whoops).

@Daniel15, @fkling is there be any disadvantage to switching the babylon parser configuration (and maybe babel5compat) to

      ['decorators', { decoratorsBeforeExport: true }],

While people definitely can specify an independent babel configuration to get around the parsing error, that's a bit of a PITA and requires extra trial and error work and time spent researching. Given that one of the primary goals (at least I think) of jscodeshift is to let people transform deprecated non-standard syntaxes into something modern and standards compliant, wouldn't it make sense to support decorators before export?

As an example, if someone right now were to try to migration their react-redux code that uses @connect to react hooks instead (per https://react-redux.js.org/api/connect), they might not be able to do so because the transform is breaking on the decoratorsBeforeExport config.

@Daniel15, @fkling is there be any disadvantage to switching the babylon parser configuration (and maybe babel5compat) to

      ['decorators', { decoratorsBeforeExport: true }],

Does this have any side effects? Could it break transforms that work today?

I thought decorators were deprecated... Do libraries still use them? Admittedly I'm not too familiar with the state of open-source JavaScript other than React any more.

@Daniel15 Am not sure about the side effects yet, Since I haven't encountered any. Also, so far in my case it didn't broke any transformations. All the transformations were successful.

Maybe we should change it in a major version bump just in case it breaks anything. We can bundle other potentially-risky changes in the same version.

I guess I neglected to mention that I did an experiment where I tried running a transform on a export before decorator piece of a code and one where the export came after the decorator. As far I can tell, the only difference is that if you have it configured
['decorators', { decoratorsBeforeExport: false }],

Then you get yelled at by babel and it refuses to go any further if you have a decorator before export.

The direction decorators are headed is detailed in https://github.com/tc39/proposal-decorators.

Also, babel7 seems to have renamed a lot of the babel package names. At some point, it might not be a bad idea for jscodeshift to update to the new names. The old package names will continue to be honored, but at some point

https://babeljs.io/docs/en/v7-migration

We could change some of these things in a major version, but as jscodeshift is currently on version 0.13.1. So if we're going by semver major versions that change will be a long ways off. 😆

I do not personally like decorators, but here I am in a codebase where I am trying to write a codemod to migrate out of typegraphql to remove all decorator annotations. But I hit a snag that the parser does not comprehend it. How do I get it to parse a file such as this one?

import { Directive, Field, ObjectType } from 'type-graphql';
import { SchemaBase } from './schemaBase';
import { Visualization } from './visualization';

@ObjectType({
  description: 'Description goes here',
})
@Directive('@cacheControl(maxAge: 200)')
export class VisualizationList {
  @Field(() => [Visualization], {
    nullable: true,
  })
  visualizations?: Visualization[];
}

EDIT: Got it working with this config.

const options = {
  dry: true,
  print: true,
  verbose: 1,
  parser: 'babylon',
  parserConfig: {
    sourceType: 'module',
    allowImportExportEverywhere: true,
    allowReturnOutsideFunction: true,
    startLine: 1,
    tokens: true,
    plugins: [
      'typescript',
      ['decorators', { decoratorsBeforeExport: true }],
    ],
  },
};

@klippx maybe a dumb question, but how did you pass the options to jscodeshift?

@piotrpalek you can use --parser-config options and pass the options as a json file