Skip to content

Commit

Permalink
LG-4105 codemods (#2322)
Browse files Browse the repository at this point in the history
* wip, remove console test

* just tinkering

* wip

* wip

* consolidate test

* add tests

* add another test

* add more checks

* attempting to fix types

* individual tests

* update index

* cleaning up

* more clean up

* remove comments

* LG-4107: codemod cli (#2327)

* rollup tweaks

* its working

* wip, messing around with cli

* undo change

* wip, cli

* migrator file

* add commenet

* clean up

* testing

* color

* update rollup file with comments

* comments

* updates

* update packaage.json

* fix test path

* remove space

* testing

* does this fix it?

* use require?

* add to external

* tests for comments

* rename test file

* update comment file

* add testing files for testing

* add prettier

* remove comment

* add comments to comment file

* credit

* update package.json

* empty commit

* add check to return the original file

* yarn lock

* update description, remove jscodeshift from scripts

* updates

* update props and docs

* more docs

* doc updates

* more docss

* some feedback

* ignore option

* update README, remove stdin option

* testing sizeDiff

* sizeDiff

* testing sizeDiff again

* cs

* clean up transformations

* update cli

* comments

* add contributing.md

* codemod

* src/migrations to src/codemods

* path default

* replace fs with fse

* add fs-extra to package.json

* remove extra test dirs

* remove console in rollup

* remove imports

* remove preactBuild

* testing main build

* revise

* add turbo to clean

* split up tests

* missed a few

* list all branches

* add origin

* remove space

* testing

* change commit

* remove restore cache

* add back cache

* remove the cache again

* address PR comments

* testing no cache

* move no cache

* add yarn build before git reset

* add another clean

* --force

* whoops

* forgot regular build

* try again

* --force again

* revert pr changes
  • Loading branch information
shaneeza authored Jun 3, 2024
1 parent dccd44f commit af20826
Show file tree
Hide file tree
Showing 59 changed files with 2,267 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/great-cougars-explode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lg-tools/cli': patch
---

Adds pre-release of codemods command
5 changes: 5 additions & 0 deletions .changeset/short-pandas-obey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lg-tools/codemods': patch
---

Pre-release of codemods.
2 changes: 1 addition & 1 deletion .github/workflows/sizeDiff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ jobs:
- uses: preactjs/compressed-size-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
build-script: 'build'
build-script: 'build --force'
clean-script: 'clean'
1 change: 1 addition & 0 deletions tools/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"dependencies": {
"@lg-tools/build": "0.5.1",
"@lg-tools/codemods": "0.0.1",
"@lg-tools/create": "0.2.7",
"@lg-tools/install": "0.1.8",
"@lg-tools/link": "0.2.3",
Expand Down
31 changes: 31 additions & 0 deletions tools/cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { buildPackage, buildTSDoc, buildTypescript } from '@lg-tools/build';
import { migrator } from '@lg-tools/codemods';
import { createPackage } from '@lg-tools/create';
import { installLeafyGreen } from '@lg-tools/install';
import { linkPackages, unlinkPackages } from '@lg-tools/link';
Expand Down Expand Up @@ -166,6 +167,36 @@ cli
)
.action(validate);

/** Migrator */
cli
.command('codemod')
.description('Runs codemod transformations to upgrade LG components')
.argument(
'<codemod>',
'One of the codemods from: https://github.com/mongodb/leafygreen-ui/blob/main/tools/codemods/README.md#codemods-1',
)
.argument(
'[path]',
'Files or directory to transform. Can be a glob like like src/**.test.js',
)
.option(
'--i, --ignore <items...>',
'Glob patterns to ignore. E.g. --i **/node_modules/** **/.next/**',
false,
)
.option('--d, --dry', 'dry run (no changes are made to files)', false)
.option(
'--p, --print',
'print transformed files to stdout, useful for development',
false,
)
.option(
'--f, --force',
'Bypass Git safety checks and forcibly run codemods',
false,
)
.action(migrator);

/** Build steps */
cli
.command('build-package')
Expand Down
7 changes: 6 additions & 1 deletion tools/cli/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@
"rootDir": "src",
"baseUrl": ".",
"paths": {
"@lg-tools/*": ["../*/src"]
"@lg-tools/*": [
"../*/src"
]
}
},
"references": [
{
"path": "../build"
},
{
"path": "../codemods"
},
{
"path": "../create"
},
Expand Down
142 changes: 142 additions & 0 deletions tools/codemods/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Contributing to codemods

## Getting Started

All codemods can be found under `src/codemods` and each codemod should have its own directory.

```
src
┣ codemods # directory for all codemods
┃ ┣ <name-of-codemod> # directory for individual codemod
┃ ┃ ┣ tests # directory for codemod tests
┃ ┃ ┃ ┃ ┣ <name-of-codemod>.input.tsx # input file for test
┃ ┃ ┃ ┃ ┣ <name-of-codemod>.output.tsx # output file for test
┃ ┃ ┃ ┃ ┗ transform.spec.ts # jest test file
┃ ┃ ┣ testing.tsx # (optional) file used to test `yarn lg codemod...`
┃ ┃ ┗ transform.ts # transformer function (file that modifies the code )
┃ ┗ ...
┗ ...
```

Utils can be found under `src/utils`

```
src
┣ utils # directory for all utils
┃ ┣ transformations # directory for reusable transformations
┃ ┃ ┣ <name-of-transformation> # directory for individual transformation
┃ ┃ ┃ ┣ tests # directory for transformation tests
┃ ┃ ┃ ┃ ┣ <test-name>.input.tsx # input file for test
┃ ┃ ┃ ┃ ┣ <test-name>.output.tsx # output file for test
┃ ┃ ┃ ┃ ┗ transform.spec.ts # jest test file
┃ ┃ ┃ ┣ <name-of-transformation>.ts # transformation code
┃ ┃ ┃ ┣ index.ts # file to add exports
┃ ┃ ┃ ┗ transform.ts # transfomer function used for testing purposes
┃ ┃ ┗ ...
┃ ┗ ...
┗ ...
```

## Creating a codemod

This package uses [jscodeshift](https://github.com/facebook/jscodeshift) to parse source code into an abstract syntax tree([AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree)).

Each codemod needs a transformer function. A transformer function takes in a file to transform, a reference to the jscodeshift library and an optional list of options. This function will parse the source code from the file into an AST, perform the transformation on the AST, then convert the modified AST back into source code.

e.g.

```jsx
/**
* Example transformer function to consolidate props
*
* @param file the file to transform
* @param jscodeshiftOptions an object containing at least a reference to the jscodeshift library
* @param options an object containing options to pass to the transform function
* @returns Either the modified file or the original file
*/
export default function transformer(
file: FileInfo,
{ jscodeshift: j }: API,
options: TransformerOptions,
) {
const source = j(file.source); // Use jscodeshift (j) to parse the source code into an AST

const {
propToRemove,
propToUpdate,
propMapping,
propToRemoveType,
componentName,
} = options;

elements.forEach(element => {
// Perform transformations on the AST
consolidateJSXAttributes({
j,
element,
propToRemove,
propToUpdate,
propMapping,
propToRemoveType,
});
});

return source.toSource(); // Return the transformed source code
}
```

Using a tool like [AST Explorer](https://astexplorer.net/) can help you visualize and experiment with Abstract Syntax Trees (ASTs)

## Testing

To test codemods, we need to create an input and output file. We then pass the input file through the transformer function and use [Jest](https://jestjs.io/) to verify if the transformed input file matches the output file.

e.g.

`input.tsx`

```tsx
import React from 'react';

const MyComponent = (props: any) => {
return <p>Testing {props.children}</p>;
};

export const App = () => {
return (
<>
<MyComponent propToUpdate="another value" />
<MyComponent propToUpdate="value" propToRemove="value2" />
</>
);
};
```

`output.tsx`

```tsx
import React from 'react';

const MyComponent = (props: any) => {
return <p>Testing {props.children}</p>;
};

export const App = () => {
return (
<>
<MyComponent propToUpdate="another value" />
<MyComponent propToUpdate="value3" />
</>
);
};
```

`test.spec.ts`

```js
const formattedOutput = prettier.format(output, { parser });
const formattedExpected = prettier.format(expected, { parser });

// Format output and expected with prettier for white spaces and line breaks consistency
expect(formattedOutput).toBe(formattedExpected);
```
148 changes: 148 additions & 0 deletions tools/codemods/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Codemods

![npm (scoped)](https://img.shields.io/npm/v/@leafygreen-ui/codemods.svg)

## Installation

### Yarn

```shell
yarn add @lg-tools/codemods
```

### NPM

```shell
npm install @lg-tools/codemods
```

## Usage

```jsx
yarn lg codemod <codemod> <path> [...options]
```

### Arguments

#### `codemod`

name of codemod, see available codemods below.

<hr>

#### `path`

files or directory to transform

<hr>

### Options

#### `--i or --ignore`

Glob patterns to ignore

```jsx
yarn lg codemod <codemode> <path> --ignore **/node_modules/** **/.next/**
```

#### `--d or --dry`

Dry run (no changes to files are made)

```jsx
yarn lg codemod <codemode> <path> --dry
```

#### `--p or --print`

Print transformed files to stdout and changes are also made to files

```jsx
yarn lg codemod <codemode> <path> --print
```

#### `--f or --force`

Bypass Git safety checks and forcibly run codemods.

```jsx
yarn lg codemod <codemode> <path> --force
```

## Codemods

**_NOTE:_ These codemods are for testing purposes only**

### `consolidate-props`

This codemod consolidates two props into one.

```jsx
yarn lg codemod codemode-props <path>
```

E.g.
In this example, the `disabled` props is merged into the `state` prop.

**Before**:

```jsx
<MyComponent disabled={true} state="valid" />
```

**After**:

```jsx
<MyComponent state="disabled" />
```

<hr>

### `rename-component-prop`

This codemod renames a component prop

```jsx
yarn lg codemod codemode-component-prop <path>
```

E.g.
In this example, `prop` is renamed to `newProp`.

**Before**:

```jsx
<MyComponent prop="hey" />
```

**After**:

```jsx
<MyComponent newProp="hey" />
```

<hr>

### `update-component-prop-value`

This codemod updates a prop value

```jsx
yarn lg codemod codemode-component-prop-value <path>
```

E.g.
In this example, `value` is updated to `new prop value`.

**Before**:

```jsx
<MyComponent prop="value" />
```

**After**:

```jsx
<MyComponent prop="new prop value" />
```
Loading

0 comments on commit af20826

Please sign in to comment.