Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [FX-4053] create codemods for RTE #3657

Merged
merged 6 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/lemon-tigers-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@toptal/picasso-codemod': minor
---

---

- add codemod for replacing RichTextEditor imports
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ packages/picasso-codemod/src/v18.0.0/picasso-lab/__testfixtures__/basic.output.t
packages/picasso-codemod/src/v20.0.0/replace-error/__testfixtures__/as-variable.input.tsx
packages/picasso-codemod/src/v20.0.0/replace-error/__testfixtures__/as-variable.output.tsx
packages/picasso-codemod/src/v20.0.0/overlay-badge/__testfixtures__/size-default.output.tsx
packages/picasso-codemod/**/__testfixtures__/*.input.tsx
packages/picasso-codemod/**/__testfixtures__/*.output.tsx
8 changes: 8 additions & 0 deletions packages/picasso-codemod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ Codemods do not guarantee the code format preservation. Therefore be sure to run

## Included Scripts

### v36.0.0

Replaces all imports of RichTextEditor related components to `@toptal/picasso-rich-text-editor` and updates package.json with a new version of `@toptal/picasso`, `@toptal/picasso-rich-text-editor` and `@toptal/picasso-forms`

```sh
npx @toptal/picasso-codemod v36.0.0 --run-in-band
```

### v52.2.0

Replaces compound `Form` with `FormNonCompound` and adjusts all the compound components to be directly imported from `picasso-forms`.
Expand Down
10 changes: 7 additions & 3 deletions packages/picasso-codemod/bin/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const require = createRequire(import.meta.url)
const codemodsDirectory = path.join(__dirname, '../', 'src')
const jscodeshift = require.resolve('.bin/jscodeshift')
const paths = {
spa: 'src/**/*.tsx',
spa: 'src/**/*.ts*',
monorepo: {
libs: '*libs/*/src/*',
hosts: '*hosts/*/src/*',
Expand All @@ -28,7 +28,7 @@ const findFilesInMonorepo = () =>
execaSync('find', [
'.',
'-name',
'*.tsx',
'*.ts*',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it used to take only TSX files

'(',
...['-path', paths.monorepo.libs],
...['-or', '-path', paths.monorepo.apps],
Expand All @@ -51,7 +51,7 @@ const findCodemodPath = codemod =>
.map(ext => path.join(codemodsDirectory, `${codemod}/index.${ext}`))
.find(fs.existsSync)

const runTransform = async ({ codemod, inputFiles, parserConfig }) => {
const runTransform = async ({ codemod, inputFiles, parserConfig, runInBand }) => {
const codemodPath = findCodemodPath(codemod)
const isMonorepo = await checkIsMonorepo()

Expand All @@ -60,6 +60,7 @@ const runTransform = async ({ codemod, inputFiles, parserConfig }) => {

args = args.concat(['--parser', 'tsx'])
args = args.concat(['--transform', codemodPath])
args = args.concat(['--run-in-band', runInBand])

if (parserConfig) {
args.push(`--parser-config=${parserConfig}`)
Expand Down Expand Up @@ -105,6 +106,7 @@ export const run = () => {

Options
--parser-config Add parser config
--run-in-band Run serially in the current process (default: false)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have to execute this codemod sequentially


Examples
$ npx @toptal/picasso-codemod v17.0.0/typography-sizes
Expand All @@ -124,10 +126,12 @@ export const run = () => {
const codemod = cli.input[0]
const inputFiles = cli.input.slice(1)
const parserConfig = cli.flags.parserConfig
const runInBand = cli.flags.runInBand

return runTransform({
codemod,
inputFiles,
parserConfig,
runInBand,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { RichTextEditor } from '@toptal/picasso-forms'
import type { CustomEmojiGroup } from '@toptal/picasso/RichTextEditor'
import type { CustomEmoji } from '@toptal/picasso/RichTextEditor/types'
import type { RichTextEditorProps as LocalRichTextEditorProps } from '@toptal/picasso'
import {
Container,
RichText as LocalRichText,
Typography,
} from '@toptal/picasso'
import { RichText } from '@toptal/picasso'
dmaklygin marked this conversation as resolved.
Show resolved Hide resolved
import { Icon } from '@toptal/picasso'
import { Something, transformString } from 'some-module'
import { htmlToHast } from '@toptal/picasso/utils'
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { RichTextEditor } from '@toptal/picasso-forms'
import type { CustomEmojiGroup } from '@toptal/picasso-rich-text-editor/RichTextEditor';
import type { CustomEmoji } from '@toptal/picasso-rich-text-editor/RichTextEditor/types';
import type { RichTextEditorProps as LocalRichTextEditorProps } from '@toptal/picasso-rich-text-editor';
import { Container, Typography } from '@toptal/picasso';
import { RichText as LocalRichText } from '@toptal/picasso-rich-text-editor';
import { RichText } from '@toptal/picasso-rich-text-editor';
OleksandrNechai marked this conversation as resolved.
Show resolved Hide resolved
import { Icon } from '@toptal/picasso'
import { Something, transformString } from 'some-module'
import { htmlToHast } from '@toptal/picasso-rich-text-editor/utils';
OleksandrNechai marked this conversation as resolved.
Show resolved Hide resolved
15 changes: 15 additions & 0 deletions packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "somemodule",
"description": "test",
"version": "0.0.3",
"author": "Toptal",
"license": "ISC",
"main": "./src/index.ts",
"dependencies": {
"@toptal/picasso": "36.0.0",
"@toptal/picasso-forms": "58.0.0",
"@toptal/picasso-rich-text-editor": "1.0.2"
},
"private": true,
"sideEffects": false
}
5 changes: 5 additions & 0 deletions packages/picasso-codemod/src/v36.0.0/__tests__/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineTest } from 'jscodeshift/src/testUtils'

defineTest(__dirname, 'rich-text-editor-replacement', {}, 'imports', {
parser: 'tsx',
})
1 change: 1 addition & 0 deletions packages/picasso-codemod/src/v36.0.0/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './rich-text-editor-replacement'
194 changes: 194 additions & 0 deletions packages/picasso-codemod/src/v36.0.0/rich-text-editor-replacement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import type { Transform } from 'jscodeshift'
import path from 'path'
import fs from 'fs'

const specificModules = [
'RichText',
'htmlToHast',
'RichTextEditor',
'RichTextEditorProps',
'ASTType',
'CustomEmojiGroup',
'CustomEmoji',
]
const picassoVersion = '36.0.0'
const picassoFormsVersion = '58.0.0'
const picassoRichTextEditorVersion = '1.0.1'

// Get current execution directory
const execDir = process.cwd()

const findPackageJson = (dirPath: string) => {
// Start with the directory provided
let currentPath = dirPath

while (currentPath !== '/' && currentPath !== execDir) {
// Attempt to read package.json at current path
try {
const packageJsonPath = path.join(currentPath, 'package.json')

if (fs.existsSync(packageJsonPath)) {
return packageJsonPath
}
} catch (err) {
console.error(err)
}

// If package.json doesn't exist, move up one directory
currentPath = path.dirname(currentPath)
}

throw new Error('Could not find package.json')
}

const updatePackageJsonVersions = (
path: string,
{ addRichTextEditorDependency }: { addRichTextEditorDependency: boolean }
) => {
try {
const packageJsonPath = findPackageJson(path)

// If it exists, read and parse it
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))

if (!packageJson.dependencies) {
return
}

let inDevDependencies = false

// Update Picasso and Picasso forms dependencies
if (packageJson.dependencies['@toptal/picasso']) {
packageJson.dependencies['@toptal/picasso'] = picassoVersion
} else if (
packageJson.devDependencies &&
packageJson.devDependencies['@toptal/picasso']
) {
packageJson.devDependencies['@toptal/picasso'] = picassoVersion
inDevDependencies = true
}

if (packageJson.dependencies['@toptal/picasso-forms']) {
packageJson.dependencies['@toptal/picasso-forms'] = picassoFormsVersion
// if picasso form is defined, it depends on RichTextEditor, we should also add a dependency
packageJson.dependencies['@toptal/picasso-rich-text-editor'] =
picassoRichTextEditorVersion
} else if (
packageJson.devDependencies &&
packageJson.devDependencies['@toptal/picasso-forms']
) {
packageJson.devDependencies['@toptal/picasso-forms'] =
picassoFormsVersion
// if picasso form is defined, it depends on RichTextEditor, we should also add a dependency
packageJson.devDependencies['@toptal/picasso-rich-text-editor'] =
picassoRichTextEditorVersion
inDevDependencies = true
}

// if RTE is used inside package, we should add a dependency
if (addRichTextEditorDependency) {
if (inDevDependencies) {
packageJson.devDependencies['@toptal/picasso-rich-text-editor'] =
picassoRichTextEditorVersion
} else {
packageJson.dependencies['@toptal/picasso-rich-text-editor'] =
picassoRichTextEditorVersion
}
}

fs.writeFileSync(
packageJsonPath,
JSON.stringify(packageJson, null, 2) + '\n'
)
} catch (err) {
console.log(`Could not parse package.json at ${packageJsonPath}: ${err}`)
}
} catch (err) {
console.log('err: ', err)
console.error(`Package json not found for ${path}: ${err}`)
}
}

const transform: Transform = (file, { jscodeshift: j }) => {
const source = j(file.source)
let fileContainsRichTextEditorImport = false
let hasPicassoForms = false

const picassoImports = source.find(j.ImportDeclaration).filter(path => {
const pattern = /@toptal\/picasso($|\/[a-z\d]*)/gi

hasPicassoForms =
hasPicassoForms || path.node.source.value === '@toptal/picasso-forms'

return (
pattern.test(path.node.source.value as string) &&
path.node.source.value !== '@toptal/picasso-forms'
)
})

// Iterate over react imports
picassoImports.forEach(picassoImport => {
const richTextSpecifiers = j(picassoImport)
.find(j.ImportSpecifier)
.filter(path => {
return specificModules.includes(path.node.imported.name)
})

// there is only single specifier, replace the whole import
if (picassoImport.node.specifiers?.length === richTextSpecifiers.length) {
j(picassoImport).replaceWith(
// Build a new import declaration node based on the existing one
j.importDeclaration(
picassoImport.node.specifiers, // copy over the existing import specificers
j.stringLiteral(
(picassoImport.node.source.value as string).replace(
'@toptal/picasso',
'@toptal/picasso-rich-text-editor'
)
),
picassoImport.node.importKind
)
)

fileContainsRichTextEditorImport = true
} else if (richTextSpecifiers.length > 0) {
// insert specifiers for rich text editor
// const importSpecifier = j.importSpecifier(richTextSpecifiers);
// Generate new specifiers for the new import
const newSpecifiers = richTextSpecifiers.nodes().map(node => {
return j.importSpecifier(
j.identifier(node.imported.name),
node.local ? j.identifier(node.local.name) : undefined
)
})
// Create a new import declaration
const newImport = j.importDeclaration(
newSpecifiers,
j.literal('@toptal/picasso-rich-text-editor'),
picassoImport.node.importKind
)

j(picassoImport).insertAfter(newImport)

// there are some specifiers left, remove the ones that are replaced
j(picassoImport)
.find(j.ImportSpecifier)
.filter(path => specificModules.includes(path.node.imported.name))
.remove()

fileContainsRichTextEditorImport = true
}
})

// we must update package.json as well. We have to upgrade @toptal/picasso, @toptal/picasso-forms and add new dependency @toptal/picasso-rich-text-editor
// first of all, we need to search for a package json, it must be in the root of the package
updatePackageJsonVersions(file.path, {
addRichTextEditorDependency:
fileContainsRichTextEditorImport || hasPicassoForms,
})

return source.toSource({ trailingComma: false, quote: 'single' })
}

export default transform
OleksandrNechai marked this conversation as resolved.
Show resolved Hide resolved