UNPKG

ntts

Version:

A CLI tool for refactoring an existing NodeJs application to a fully functional TypeScript application.

267 lines (242 loc) 11.1 kB
import {Project} from 'ts-morph'; import {Dirent, readdirSync} from 'fs'; import ignore, {Ignore} from 'ignore'; import {join} from 'path'; import ImportsRefactor from './imports-refactor/imports-refactor'; import ClassRefactor from './class-refactor/class-refactor'; import ExportsRefactor from './exports-refactor/exports-refactor'; import ModuleSpecifierRefactorModel from '../models/module-specifier-refactor.model'; import Logger from '../logger/logger'; import TsconfigHandler from '../tsconfig-handler/tsconfig-handler'; import TypesRefactor from './types-refactor/types-refactor'; import {generateProgressBar} from './helpers/generate-progress-bar/generate-progress-bar'; import {addTypeAlias, getTypeAliasType} from './types-refactor/interface-handler/interface-creator/interface-creator'; class CodeRefactor { static convertToTypescript = (project: Project, target: string, unknown: boolean) => { this.refactorExports(project); this.refactorImports(project); this.refactorClasses(project); this.addTypeAlias(project, target, unknown); this.generateInterfaces(project, target); this.inferParameterTypes(project, target); this.inferFunctionTypeParameterTypes(project, target); this.inferParameterTypes(project, target); this.inferFunctionTypeParameterTypes(project, target); this.setFunctionReturnTypes(project, target); this.inferWriteAccessType(project, target); this.inferContextualType(project, target); this.inferInterfaceProperties(project, target); this.replaceTypes(project, target); this.mergeInterfaces(project, target); this.filterUnionType(project); this.mergeInterfaces(project, target); this.removeUnnecessaryTypeNodes(project); this.refactorImportTypesAndGlobalVariables(project); this.simplifyOptionalNodes(project, target); this.simplifyTypeNodes(project, target); }; static addSourceFiles = (ignores: string[], path: string): Project => { Logger.info('Loading project files'); const project = new Project({ tsConfigFilePath: TsconfigHandler.tsconfigFileName(), skipAddingFilesFromTsConfig: true, }); const ig = ignore().add(ignores); this.readDirectory(project, path || '.', ig); return project; }; private static readDirectory = (project: Project, path: string, ig: Ignore): Project => { readdirSync(path, {withFileTypes: true}) .forEach((item) => this.checkDirectoryEntry(project, item, path, ig)); return project; }; private static checkDirectoryEntry = (project: Project, item: Dirent, path: string, ig: Ignore): Project => { const fullPath = join(path, item.name); const ignores = ig.ignores(fullPath); if (!ignores && item.isFile() && fullPath.endsWith('.ts')) { project.addSourceFileAtPath(fullPath); } if (!ignores && item.isDirectory()) { this.readDirectory(project, fullPath, ig); } return project; }; private static refactorExports = (project: Project) => { Logger.info('Refactoring exports'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach((s) => { ExportsRefactor.moduleExportsToExport(s); bar.tick(); }); Logger.success('Exports refactored'); } private static refactorImports = (project: Project) => { Logger.info('Refactoring requires to imports'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); const modulesResult = sourceFiles.reduce((moduleSpecifierResult: ModuleSpecifierRefactorModel, s) => { ImportsRefactor.requiresToImports(s); ImportsRefactor.refactorImportClauses(s); const result = ImportsRefactor.reformatImports(s, moduleSpecifierResult); bar.tick(); return result; }, {fileEndings: []}); ImportsRefactor.resolveModuleSpecifierResults(modulesResult); Logger.success('Requires refactored'); } private static refactorClasses = (project: Project) => { Logger.info('Refactoring classes'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach((s) => { ClassRefactor.toTypeScriptClasses(s); bar.tick(); }); Logger.success('Classes refactored'); } private static generateInterfaces = (project: Project, target: string) => { Logger.info('Generating interfaces from object literal types'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach((s) => { TypesRefactor.createInterfacesFromObjectTypes(s, project, target); bar.tick(); }); Logger.success('Generated interfaces from object literal types where possible'); } private static inferParameterTypes = (project: Project, target: string) => { Logger.info('Declaring parameter types by usage'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach(s => { TypesRefactor.inferParameterTypes(s, project, target); bar.tick(); }); Logger.success('Parameter type declared where possible'); } private static inferFunctionTypeParameterTypes = (project: Project, target: string) => { Logger.info('Declaring parameter types of function types by usage'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach(s => { TypesRefactor.inferFunctionTypeParameterTypes(s, project, target); bar.tick(); }); Logger.success('Parameter type declared where possible'); } private static inferWriteAccessType = (project: Project, target: string) => { Logger.info('Declaring variable and property types by write access'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach((s) => { TypesRefactor.inferWriteAccessType(s, project, target); bar.tick(); }); Logger.success('Variable and Property type declared where possible'); }; private static inferInterfaceProperties = (project: Project, target: string) => { Logger.info('Checking usage of generated interfaces for additional properties and types'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach((s) => { TypesRefactor.inferInterfaceProperties(s, project, target); bar.tick(); }); Logger.success('Defined type and added properties to interfaces where possible'); } private static inferContextualType = (project: Project, target: string) => { Logger.info('Inferring type of untyped declarations by contextual type'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach((s) => { TypesRefactor.inferContextualType(s, project, target); bar.tick(); }); Logger.success('Inferred type where possible'); } private static replaceTypes = (project: Project, target: string) => { Logger.info('Replacing undesirable types'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); const typeAlias = getTypeAliasType(project, target); sourceFiles.forEach((s) => { TypesRefactor.replaceInvalidTypes(s, typeAlias); bar.tick(); }); Logger.success('Replaced undesirable types'); } private static mergeInterfaces = (project: Project, target: string) => { Logger.info('Merging duplicate interfaces'); TypesRefactor.mergeDuplicateInterfaces(project, target); Logger.success('Merged duplicate interfaces where possible'); } private static refactorImportTypesAndGlobalVariables = (project: Project) => { Logger.info('Refactoring import types to simple type references and importing global variables'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach((s) => { TypesRefactor.refactorImportTypesAndTypeReferences(s); bar.tick(); }); Logger.success('Refactored import types to simple type references and imported global variables where possible'); } private static filterUnionType = (project: Project) => { Logger.info('Filtering out duplicate types in union types'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach((s) => { TypesRefactor.filterUnionType(s); bar.tick(); }); Logger.success('Filtered union types'); } private static simplifyOptionalNodes = (project: Project, target: string) => { Logger.info('Removing undefined type from optional node'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); const typeAlias = getTypeAliasType(project, target); sourceFiles.forEach((s) => { TypesRefactor.removeUndefinedFromOptional(s, typeAlias); bar.tick(); }); Logger.success('Removed undefined types'); } private static simplifyTypeNodes = (project: Project, target: string) => { Logger.info('Removing null or undefined types'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); const typeAlias = getTypeAliasType(project, target); sourceFiles.forEach((s) => { TypesRefactor.removeNullOrUndefinedTypes(s, typeAlias); bar.tick(); }); Logger.success('Removed null or undefined types'); } private static addTypeAlias = (project: Project, target: string, unknown: boolean) => { Logger.info(`Adding type alias of type ${unknown ? 'unknown' : 'any'}`); addTypeAlias(project, target, unknown); Logger.success('Added type alias'); } private static setFunctionReturnTypes = (project: Project, target: string) => { Logger.info('Replacing object types of function returns with interfaces'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach((s) => { TypesRefactor.setFunctionReturnTypes(s, project, target); bar.tick(); }); Logger.success('Replaced object types of function returns with interfaces'); } private static removeUnnecessaryTypeNodes = (project: Project) => { Logger.info('Removing unnecessary type nodes'); const sourceFiles = project.getSourceFiles(); const bar = generateProgressBar(sourceFiles.length); sourceFiles.forEach((s) => { TypesRefactor.removeUnnecessaryTypeNodes(s); bar.tick(); }); Logger.success('Removed unnecessary type nodes'); } } export default CodeRefactor;