Giter VIP home page Giter VIP logo

optional-chaining-codemod's Introduction

Optional chaining codemod

Build Status npm version PRs Welcome

This is a codemod to migrate different types of lodash get calls and a && a.b kind of expressions to use optional chaining and nullish coalescing instead.

Following babel plugins are required to transpile optional chaining and nullish coalescing:

What it does?

  • a && a.b becomes a?.b
  • _.get(foo, 'a.b') and _.get(foo, ['a', 'b']) becomes foo?.a?.b
  • _.get(foo, 'a.b', defaultValue) becomes foo?.a?.b ?? defaultValue

You can check out the __textfixtures__ folder to see full list of supported transformations.

Why should I migrate to use optional chaining?

  • When using static type checkers like Flow or Typescript, optional chaining provides much better type safety than lodash get. Optional chaining is standard Javascript feature.
  • It also has a neater syntax than chaining && expressions one after another.

Install

$ yarn global add optional-chaining-codemod

or

$ npm install -g optional-chaining-codemod

Usage

$ optional-chaining-codemod ./**/*.js --ignore-pattern="**/node_modules/**"

with flow parser:

$ optional-chaining-codemod ./**/*.js --ignore-pattern="**/node_modules/**" --parser=flow

with typescript parser:

$ optional-chaining-codemod ./**/*.ts --ignore-pattern="**/node_modules/**" --parser=ts

with typescript+react parser:

$ optional-chaining-codemod ./**/*.tsx --parser=tsx

The CLI is the same as in jscodeshift except you can omit the transform file.

Alternatively, you can run the codemod using jscodeshift as follows:

$ yarn global add jscodeshift
$ yarn add optional-chaining-codemod
$ jscodeshift -t node_modules/optional-chaining-codemod/transform.js --ignore-pattern="**/node_modules/**" ./**/*.js

flags

This codemod has two flags:

  1. --skipVariables to skip variables passed to lodash get
  2. --skipTemplateStrings to skip template strings passed to lodash get

Especially the first case is risky as the variable might actually be something like var bar = "a.b.c" and produce from _.get(foo, bar) following: foo?[bar] although lodash would treat it like foo?.a?.b?.c".

Contributing

Contributions are more than welcome! One area of improvement could be e.g better CLI or finding out new areas to migrate to use optional chaining.

optional-chaining-codemod's People

Contributors

chimurai avatar connorjclark avatar daveisfera avatar dependabot[bot] avatar devjv avatar fauxfaux avatar jorgegonzalez avatar lautis avatar thekip avatar villesau avatar xyy94813 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

optional-chaining-codemod's Issues

array syntax

looks like i might have found an edge case for you:

     get(foo, 'bar[0]["60"]')

is transforming to

      foo?.bar?.0]["60"

Doesn't handle passing array reference as path

This example

import { get } from 'lodash';

const obj = { a: { b: { c: { d: 1 }}}};
const path = ['a','b','c','d'];

get(obj, path);

Turns into:

const obj = { a: { b: { c: { d: 1 }}}};
const path = ['a','b','c','d'];

obj?.[path];

Which is incorrect, obv. Will try to do a follow-up PR to tackle this issue.

another edge case

input

const id = correlations && correlations.id;

output

const { id } = correlations;

expected output

const id = correlations?.id;

Doesn't handle curried versions of `get()` and `getOr()`

One last big thanks for the awesome codemod!

Curried versions of get() and getOr() cause an error:

TypeError: Cannot read property 'type' of undefined
    at skip (/Users/dlj/projects/optional-chaining-codemod/transform.js:82:35)
    at NodePath.<anonymous> (/Users/dlj/projects/optional-chaining-codemod/transform.js:133:9)
    at NodePath.<anonymous> (/usr/local/lib/node_modules/jscodeshift/src/collections/Node.js:142:47)
    at /usr/local/lib/node_modules/jscodeshift/src/Collection.js:75:36
    at Array.forEach (<anonymous>)
    at Collection.forEach (/usr/local/lib/node_modules/jscodeshift/src/Collection.js:74:18)
    at Collection.replaceWith (/usr/local/lib/node_modules/jscodeshift/src/collections/Node.js:140:17)
    at Collection.replaceWith (/usr/local/lib/node_modules/jscodeshift/src/Collection.js:413:43)
    at mangleLodashGets (/Users/dlj/projects/optional-chaining-codemod/transform.js:132:8)
    at module.exports (/Users/dlj/projects/optional-chaining-codemod/transform.js:313:3)

But they could be turned into fat arrow functions using the existing logic, like the following:
get('value') to v => v?.value
getOr(default, 'value') to v => v?.value ?? default

`some.object && some.object.fn()` and `this.object && this.object.value` not handled

I used this tool on a large codebase, and came across some cases that were missed. Here's
everything I found:

GoogleChrome/lighthouse#13503 (comment)
(link to code at particular commit)
https://github.com/GoogleChrome/lighthouse/tree/11a6a030f3e29910be6dc1166f17e085c17da6da

I believe the following specific examples show every unique case missed.


Does not transform:

const hasMetricError = lhr.categories.performance && lhr.categories.performance.auditRefs
  .some(audit => Boolean(audit.group === 'metrics' && lhr.audits[audit.id].errorMessage));

But it will transform this:

const hasMetricError = lhr.categories.performance && lhr.categories.performance.auditRefs;

Perhaps function calls are not handled?


Does not transform:

this.json.audits['script-treemap-data'] && this.json.audits['script-treemap-data'].details

or

this.json.audits.blah && this.json.audits.blah.details

Perhaps this. is not handled?

Could further reduce `.length > 0` checks

Thanks once again for this saaaaweeeet codemod!

This line of code item && item.props && item.props.data && item.props.data.length > 0 is reduced to two statementsitem?.props?.data && item.props.data.length > 0 but it could be reduced to a single statement item?.props?.data?.length > 0

`b && b[0]` -> `b?.[0]` makes me nauseous

b && b[0] currently gets transformed to b?.[0].

Personally, I'm not ready for this monster. ?.[. Just look at it.

Could this specific transform be made optional, please?

`import * as _ from "lodash"` not handled

The "recommended" way to import lodash in TS is import * as _ from "lodash";, the import _ from "lodash"; is Kinda Legacy(tm). The tool does not support this style.

If you change the import in typescript.input.ts, the import and the calls on it are ignored:

  ● lodash get to optional chaining › flags › typescript › transform › transforms correctly using "typescript" data

    expect(received).toEqual(expected) // deep equality

    - Expected
    + Received

    + import * as _ from "lodash";
    +
    - const foo1  = bar?.a?.b?.c;
    + const foo1  = _.get(bar, "a.b.c");
      const foo2  = bar?.a?.b?.c;
...
      const foo9  = bar?.a?.[foo5]?.c ?? "what";
      const foo10 = bar?.a?.[foo5]?.c ?? barr;
    - const foo11 = bar?.a?.[foo5];
    + const foo11 = _.get(bar, `a.${foo5}`);
    - const foo12 = bar?.a?.[foo5]?.smthng;
...

nested w/ array elements

found Another use case. :)

input:

foo && foo[0] && foo[0].bar

output:

foo?.[0] && foo[0].bar

expected:

foo?.[0]?.bar

Doesn't put "invalid" names in string

Thanks for this awesome codemod!

While running on our code, we noticed that it doesn't handle names that need to be made strings correctly. For example, get(['value', 'something-else'], data) is changed to data?.value?.something-else when it should be data?.value?.['something-else']

kudos

I would like to file an issue giving you Kudos. Bless you for making this codemod. no really. this is so good. bless your heart!

dashes use case

get(foo, "bar.data-thing-a-mabob");

transforms into this which is invalid:

 foo?.bar?.data-thing-a-mabob;

should be transformed to

foo?.bar?.["data-thing-a-mabob"]

Doesn't reduce `getOr(false, ...)` correctly

Thanks again for this awesome codemod!

getOr(false, 'value', data) is kind of silly, but it's reduced to data?.value ?? false when it should just be data?.value since it will already be falsy and eslint doesn't like ?? false

bug on edge case

input

const padding = (info.padding && info.padding[info.level]) || '';

output

const padding = info.padding?.info.level || '';

it should be:

const padding = info.padding?[info.level] || '';

Transformation error (did not recognize object of type "ChainExpression")

Seems the codemod does not support running on code that already utilizes optional chaining.

lighthouse-core/computed/metrics/timing-summary.js Transformation error (did not recognize object of type "ChainExpression")
Error: did not recognize object of type "ChainExpression"
    at Object.getFieldNames (/Users/cjamcl/.npm/_npx/42135/lib/node_modules/optional-chaining-codemod/node_modules/ast-types/lib/types.js:661:19)
    at visitChildren (/Users/cjamcl/.npm/_npx/42135/lib/node_modules/optional-chaining-codemod/node_modules/ast-types/lib/path-visitor.js:186:36)
    at Visitor.PVp.visitWithoutReset (/Users/cjamcl/.npm/_npx/42135/lib/node_modules/optional-chaining-codemod/node_modules/ast-types/lib/path-visitor.js:168:20)
    at visitChildren (/Users/cjamcl/.npm/_npx/42135/lib/node_modules/optional-chaining-codemod/node_modules/ast-types/lib/path-visitor.js:205:25)
    at Visitor.PVp.visitWithoutReset (/Users/cjamcl/.npm/_npx/42135/lib/node_modules/optional-chaining-codemod/node_modules/ast-types/lib/path-visitor.js:168:20)
    at NodePath.each (/Users/cjamcl/.npm/_npx/42135/lib/node_modules/optional-chaining-codemod/node_modules/ast-types/lib/path.js:89:26)
    at visitChildren (/Users/cjamcl/.npm/_npx/42135/lib/node_modules/optional-chaining-codemod/node_modules/ast-types/lib/path-visitor.js:180:18)
    at Visitor.PVp.visitWithoutReset (/Users/cjamcl/.npm/_npx/42135/lib/node_modules/optional-chaining-codemod/node_modules/ast-types/lib/path-visitor.js:168:20)
    at visitChildren (/Users/cjamcl/.npm/_npx/42135/lib/node_modules/optional-chaining-codemod/node_modules/ast-types/lib/path-visitor.js:205:25)
    at Visitor.PVp.visitWithoutReset (/Users/cjamcl/.npm/_npx/42135/lib/node_modules/optional-chaining-codemod/node_modules/ast-types/lib/path-visitor.js:168:20)

I believe it was resolved here: benjamn/ast-types#383

Add support of custom babel rules

Hi, thank you for your package. It's great and helps us a lot to migrate our codebase to modern JS style.

In our code we use custom babel plugins, such as @babel/plugin-proposal-decorators
When I apply your tool to our code base, I receive erros, like

SyntaxError: This experimental syntax requires enabling one of the following parser plugin(s): 'decorators-legacy, decorators' (11:0)

How I can fix them?
Thanks!

.prettierrc missing?

Please commit your .prettierrc, or upgrade prettier to 2.0, with the sane defaults (which the codebase is formatted using), e.g. trailing-comma.

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.