Created
December 30, 2021 11:19
-
-
Save fnnzzz/7fd967b5396dbadcb91d445cb4196f22 to your computer and use it in GitHub Desktop.
refactoring pipes via ts-morph
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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