Skip to content

Instantly share code, notes, and snippets.

@fnnzzz
Created December 30, 2021 11:19
Show Gist options
  • Save fnnzzz/7fd967b5396dbadcb91d445cb4196f22 to your computer and use it in GitHub Desktop.
Save fnnzzz/7fd967b5396dbadcb91d445cb4196f22 to your computer and use it in GitHub Desktop.
refactoring pipes via ts-morph
import { Project, SyntaxKind } from 'ts-morph'
import { join } from 'path'
const sharedPipes = new Project({})
sharedPipes.addSourceFilesAtPaths(join(__dirname, '../../libs/shared/pipes/**/(*.pipe.ts)'))
/* делаем словарик интересующих нас пайпов, на выходе будет массив вроде такого
* {
importPath: '@alliance/shared/pipes/time-period',
pipeName: 'timePeriod',
moduleName: 'SharedPipesTimePeriodModule'
},
{
importPath: '@alliance/shared/pipes/to-base64',
pipeName: 'toBase64',
moduleName: 'SharedPipesToBase64Module'
},
...
* */
const targetPipes: Array<{ pipeName: string; moduleName: string; importPath: string }> = sharedPipes
.getSourceFiles()
.map(pipe => {
// забираем `name` из декоратора @Pipe
const pipeFullText = pipe.getClasses()[0].getDecorators()[0].getArguments()[0].getFullText()
// eslint-disable-next-line no-restricted-properties,@typescript-eslint/no-unsafe-member-access
const pipeName = JSON.parse(pipeFullText.replace('name', '"name"').replace('pure', '"pure"').replace(/'/gm, '"'))[
'name'
] as string
const pathToPipes = pipe.getDirectoryPath().replace('src/lib', '')
const moduleProject = new Project({})
// имя модуля и путь для импорта, чтобы можно было вставить его в модуль, который раньше юзал `SharedPipesModule`
const [moduleName] = moduleProject
.addSourceFilesAtPaths(join(pathToPipes, '**/*.module.ts'))
.map(module => module.getClasses()[0].getStructure().name)
const packageJsons = new Project({})
const [importPath] = moduleProject
.addSourceFilesAtPaths(join(pathToPipes, 'package.json'))
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-return,no-restricted-properties,@typescript-eslint/no-unsafe-argument
.map(pkgJsn => JSON.parse(pkgJsn.getStructure().statements[0])['name']) as string[]
return {
importPath,
pipeName,
moduleName
}
})
const projectModules = new Project({})
projectModules.addSourceFilesAtPaths(join(__dirname, '../../libs/**/*.module.ts'))
projectModules.getSourceFiles().forEach(file => {
// eslint-disable-next-line sonarjs/cognitive-complexity
file.getImportDeclarations().forEach(importDeclaration => {
// интересующие нас модули те, у которых есть шаренные пайпы в импортах
if (importDeclaration.getStructure().moduleSpecifier === '@alliance/shared/pipes') {
const htmlProject = new Project({})
// в папке этого модуля ищем все хтмльки и забираем список пайпов в них
htmlProject.addSourceFilesAtPaths(`${file.getDirectoryPath()}/**/*.html`)
htmlProject.getSourceFiles().forEach(htmlFile => {
const pipes = Array.from(
// исключаем неинтересующие нас пайпы (async, translate etc)
// и убираем дубликаты
new Set(
(htmlFile.getFullText().match(/\s\|\s\w+/gm) || [])
.map(pipe => pipe.replace('|', '').trim())
.filter(pipe => targetPipes.some(targetPipe => targetPipe.pipeName === pipe))
)
)
if (pipes.length) {
// проходимся по всем импортам модуля, удаляем SharedPipesModule
file.getImportDeclarations().forEach(_import => {
if (_import.getModuleSpecifier().getLiteralValue().trim() === '@alliance/shared/pipes') {
_import.remove()
}
})
pipes.forEach(pipe => {
const pipeMeta = targetPipes.find(targetPipe => targetPipe.pipeName === pipe)
// проходимся по каждому пайпу и добавляем импорт с ним в модуль, где раньше был SharedPipesModule
if (!file.getFullText().includes(pipeMeta.importPath)) {
file.addImportDeclaration({
moduleSpecifier: pipeMeta.importPath,
namedImports: [{ name: pipeMeta.moduleName }]
})
}
/* тут самый хардкор, в тс-морфе есть сложность с нормальным обходом аргументов декоратора
* в `ng-morph` с этим попроще, но там надо отдельно поприседать, чтобы его завести
* поэтому тут местами костыли, чтобы завести все это дело
* */
file
.getClasses()[0]
.getDecorators()[0]
.getArguments()[0]
.forEachChild(child => {
child.forEachChild(_child => {
if (
_child.getKindName() === 'ArrayLiteralExpression' &&
// отсекаем массивы `exports, declarations, providers` и пр.
_child.getParent().getFullText().includes('imports')
) {
const array = _child.asKindOrThrow(SyntaxKind.ArrayLiteralExpression)
_child.forEachChild(__child => {
/* тут не нашел нормального способа удалить `SharedPipesModule`
* поэтому вместо удаления делаю replace и уже после добавляю остальные пайпы
* через addElement
* */
array.getElements().forEach(element => {
if (element.getFullText().trim() === 'SharedPipesModule') {
element.replaceWithText(pipeMeta.moduleName)
}
})
if (!array.getFullText().includes(pipeMeta.moduleName)) {
array.addElement(pipeMeta.moduleName)
}
})
}
})
})
})
file.saveSync()
}
})
}
})
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment