Skip to content

Commit

Permalink
chore: add option to write files after the codemod (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
valerybugakov authored Aug 31, 2021
1 parent 7d2d449 commit 31a4920
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 25 deletions.
3 changes: 1 addition & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import { Project } from 'ts-morph'

import { globalCssToCssModule } from './transforms/globalCssToCssModule/globalCssToCssModule'

// TODO: add interactive CLI support
const TARGET_FILE = path.resolve(__dirname, './transforms/globalCssToCssModule/__tests__/fixtures/Kek.tsx')

async function main(): Promise<void> {
const project = new Project()
project.addSourceFilesAtPaths(TARGET_FILE)

const result = await globalCssToCssModule(project)
const result = await globalCssToCssModule({ project, shouldWriteFiles: true })
console.log(result)
}

Expand Down
15 changes: 15 additions & 0 deletions src/transforms/globalCssToCssModule/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Global CSS to CSS Module codemod

Status: WIP

## Usage

```sh
echo "Hello world"
```

## TODO

[] Handle @import statements in SCSS
[] Run code formatters on the updated files
[] Add interactive CLI support
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('globalCssToCssModule', () => {
it('transforms correctly', async () => {
const project = new Project()
project.addSourceFilesAtPaths(TARGET_FILE)
const [transformResult] = await globalCssToCssModule(project)
const [transformResult] = await globalCssToCssModule({ project })

expect(transformResult.css.source).toMatchSnapshot()
expect(transformResult.ts.source).toMatchSnapshot()
Expand Down
85 changes: 68 additions & 17 deletions src/transforms/globalCssToCssModule/globalCssToCssModule.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { readFileSync } from 'fs'
import { existsSync, readFileSync, promises as fsPromises } from 'fs'
import path from 'path'

import { Project } from 'ts-morph'

import { isDefined } from '../../utils'

import { getCssModuleExportNameMap } from './postcss/getCssModuleExportNameMap'
import { transformFileToCssModule } from './postcss/transformFileToCssModule'
import { addClassNamesUtilImportIfNeeded } from './ts/classNamesUtility'
import { getNodesWithClassName } from './ts/getNodesWithClassName'
import { STYLES_IDENTIFIER, processNodesWithClassName } from './ts/processNodesWithClassName'

interface GlobalCssToCssModuleOptions {
project: Project
/** If `true` persist changes made by the codemod to the filesystem. */
shouldWriteFiles?: boolean
}

interface CodemodResult {
css: {
source: string
Expand All @@ -18,6 +26,7 @@ interface CodemodResult {
source: string
path: string
}
fsWritePromise?: Promise<void[]>
}

/**
Expand All @@ -32,44 +41,86 @@ interface CodemodResult {
* 7) Add `.module.scss` import to the `.tsx` file.
*
*/
export function globalCssToCssModule(project: Project): Promise<CodemodResult[]> {
const codemodResults = project.getSourceFiles().map(async sourceFile => {
const filePath = sourceFile.getFilePath()
export async function globalCssToCssModule(options: GlobalCssToCssModuleOptions): Promise<CodemodResult[]> {
const { project, shouldWriteFiles } = options
/**
* Find `.tsx` files with co-located `.scss` file.
* For example `RepoHeader.tsx` should have matching `RepoHeader.scss` in the same folder.
*/
const itemsToProcess = project
.getSourceFiles()
.map(tsSourceFile => {
const tsFilePath = tsSourceFile.getFilePath()

const parsedTsFilePath = path.parse(tsFilePath)
const cssFilePath = path.resolve(parsedTsFilePath.dir, `${parsedTsFilePath.name}.scss`)

if (existsSync(cssFilePath)) {
return {
tsSourceFile,
cssFilePath,
}
}

return undefined
})
.filter(isDefined)

const parsedTsFilePath = path.parse(filePath)
const cssFilePath = path.resolve(parsedTsFilePath.dir, `${parsedTsFilePath.name}.scss`)
const codemodResultPromises = itemsToProcess.map(async ({ tsSourceFile, cssFilePath }) => {
const tsFilePath = tsSourceFile.getFilePath()
const parsedTsFilePath = path.parse(tsFilePath)

// TODO: add check if SCSS file doesn't exist and exit if it's not found.
const sourceCss = readFileSync(cssFilePath, 'utf8')
const { css: cssModuleSource, filePath: cssModuleFileName } = await transformFileToCssModule(
const { css: cssModuleSource, filePath: cssModuleFileName } = await transformFileToCssModule({
sourceCss,
cssFilePath
)
sourceFilePath: cssFilePath,
})
const exportNameMap = await getCssModuleExportNameMap(cssModuleSource)

processNodesWithClassName({
exportNameMap,
nodesWithClassName: getNodesWithClassName(sourceFile),
nodesWithClassName: getNodesWithClassName(tsSourceFile),
})

addClassNamesUtilImportIfNeeded(sourceFile)
sourceFile.addImportDeclaration({
addClassNamesUtilImportIfNeeded(tsSourceFile)
tsSourceFile.addImportDeclaration({
defaultImport: STYLES_IDENTIFIER,
moduleSpecifier: `./${path.parse(cssModuleFileName).base}`,
})

// TODO: run prettier and eslint --fix over updated files.
/**
* If `shouldWriteFiles` is true:
*
* 1. Update TS file with a new source that uses CSS module.
* 2. Create a new CSS module file.
* 3. Delete redundant SCSS file that's replaced with CSS module.
*/
const fsWritePromise = shouldWriteFiles
? Promise.all([
tsSourceFile.save(),
fsPromises.writeFile(cssModuleFileName, cssModuleSource, { encoding: 'utf-8' }),
fsPromises.rm(cssFilePath),
])
: undefined

return {
fsWritePromise,
css: {
source: cssModuleSource,
path: path.resolve(parsedTsFilePath.dir, cssModuleFileName),
},
ts: {
source: sourceFile.getFullText(),
path: sourceFile.getFilePath(),
source: tsSourceFile.getFullText(),
path: tsSourceFile.getFilePath(),
},
}
})

return Promise.all(codemodResults)
const codemodResults = await Promise.all(codemodResultPromises)

if (shouldWriteFiles) {
await Promise.all(codemodResults.map(result => result.fsWritePromise))
}

return codemodResults
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const replaceWhitespace = (value: string) => value.replace(/\s+/g, ' ').trim()

describe('transformFileToCssModule', () => {
it('correctly transforms provided CSS to CSS module', async () => {
const cssSource = `
const sourceCss = `
// .repo-header comment
.repo-header {
flex: none;
Expand Down Expand Up @@ -72,7 +72,7 @@ describe('transformFileToCssModule', () => {
}
`

const { css, filePath } = await transformFileToCssModule(cssSource, 'whatever.scss')
const { css, filePath } = await transformFileToCssModule({ sourceCss, sourceFilePath: 'whatever.scss' })

expect(replaceWhitespace(css)).toEqual(replaceWhitespace(expectedCssModuleSource))
expect(filePath).toEqual('whatever.module.scss')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@ import path from 'path'
import { createCssProcessor } from './createCssProcessor'
import { postcssToCssModulePlugin } from './postcssToCssModulePlugin'

interface TransformFileToCssModuleOptions {
sourceCss: string
sourceFilePath: string
}

interface TransformFileToCssModuleResult {
css: string
filePath: string
}

export async function transformFileToCssModule(
sourceCss: string,
sourceFilePath: string
options: TransformFileToCssModuleOptions
): Promise<TransformFileToCssModuleResult> {
const { sourceCss, sourceFilePath } = options

const transformFileToCssModuleProcessor = createCssProcessor(postcssToCssModulePlugin())
const transformedResult = await transformFileToCssModuleProcessor(sourceCss)

const { dir, name } = path.parse(sourceFilePath)
const newFilePath = path.join(dir, `${name}.module.scss`)

// TODO: add option to write files.
return {
css: transformedResult.css,
filePath: newFilePath,
Expand Down
3 changes: 3 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isDefined<T>(argument: T | undefined): argument is T {
return argument !== undefined
}

0 comments on commit 31a4920

Please sign in to comment.