├── README.md ├── find_ts_error_lines.ts ├── fix_import_module.js ├── fix_import_star.js ├── fix_node_module.ts └── fix_ts_ignore_in_jsx.js /README.md: -------------------------------------------------------------------------------- 1 | # js_to_ts 2 | -------------------------------------------------------------------------------- /find_ts_error_lines.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import * as ts from 'typescript'; 3 | import * as fs from 'fs'; 4 | 5 | function compile(fileNames: string[], options: ts.CompilerOptions): void { 6 | const program = ts.createProgram(fileNames, options); 7 | const emitResult = program.emit(); 8 | 9 | const allDiagnostics = ts 10 | .getPreEmitDiagnostics(program) 11 | .concat(emitResult.diagnostics); 12 | 13 | const fileToLines = {}; 14 | allDiagnostics.forEach(diagnostic => { 15 | if (diagnostic.file) { 16 | const { 17 | line, 18 | character, 19 | } = diagnostic.file.getLineAndCharacterOfPosition( 20 | diagnostic.start!, 21 | ); 22 | const message = ts.flattenDiagnosticMessageText( 23 | diagnostic.messageText, 24 | '\n', 25 | ); 26 | // console.log( 27 | // `${diagnostic.file.fileName} (${line + 1},${character + 28 | // 1}): ${message}`, 29 | // ); 30 | if (!diagnostic.file.fileName.includes('/node_modules/')) { 31 | const lines = fileToLines[diagnostic.file.fileName] || []; 32 | lines.push(line + 1); 33 | fileToLines[diagnostic.file.fileName] = lines; 34 | } 35 | } else { 36 | // console.log( 37 | // `${ts.flattenDiagnosticMessageText( 38 | // diagnostic.messageText, 39 | // '\n', 40 | // )}`, 41 | // ); 42 | } 43 | }); 44 | const jsonFileToLines = JSON.stringify(fileToLines); 45 | console.log(jsonFileToLines); 46 | // fs.writeFile(outFileName, jsonFileToLines, err => { 47 | // if (err) { 48 | // return console.error(err); 49 | // } 50 | // console.log('File saved'); 51 | // }); 52 | 53 | const exitCode = emitResult.emitSkipped ? 1 : 0; 54 | // console.log(`Process exiting with code '${exitCode}'.`); 55 | process.exit(exitCode); 56 | } 57 | 58 | compile(process.argv.slice(2), { 59 | allowSyntheticDefaultImports: true, 60 | noFallthroughCasesInSwitch: true, 61 | noUnusedParameters: true, 62 | noImplicitReturns: true, 63 | moduleResolution: ts.ModuleResolutionKind.NodeJs, 64 | esModuleInterop: true, 65 | noUnusedLocals: true, 66 | noImplicitAny: true, 67 | target: ts.ScriptTarget.ESNext, 68 | // module: ts.ModuleKind.CommonJS, 69 | strict: true, 70 | jsx: ts.JsxEmit.React, 71 | composite: true, 72 | declarationDir: './decl', 73 | outDir: './dist/', 74 | sourceMap: true, 75 | strictNullChecks: true, 76 | alwaysStrict: true, 77 | suppressImplicitAnyIndexErrors: false, 78 | }); 79 | 80 | -------------------------------------------------------------------------------- /fix_import_module.js: -------------------------------------------------------------------------------- 1 | const directImport = (name, root, j) => { 2 | let res = false; 3 | root.find(j.NewExpression).forEach(path => { 4 | try { 5 | if (path.value.callee.name === name) { 6 | res = true; 7 | } 8 | } catch (e) {} 9 | }); 10 | root.find(j.ClassDeclaration).forEach(path => { 11 | try { 12 | if (path.value.superClass.name === name) { 13 | res = true; 14 | } 15 | } catch (e) {} 16 | }); 17 | return res; 18 | }; 19 | 20 | const inlineRequire = (root, j) => { 21 | root.find(j.CallExpression).forEach(path => { 22 | try { 23 | if (path.value.callee.name !== "require" || path.parentPath.parentPath.parentPath.parentPath.name === "body") { 24 | return; 25 | } 26 | const file = path.value.arguments[0].value; 27 | const name = 28 | file 29 | .split("/") 30 | .slice(-1) 31 | .pop() 32 | .replace(/([-_]\w)/g, g => g[1].toUpperCase()) + "Module"; 33 | root.get().node.program.body.unshift("import " + name + " from '" + file + "';"); 34 | path.replace(name); 35 | } catch (e) {} 36 | }); 37 | }; 38 | 39 | export default (file, api: core.API, options) => { 40 | const j = api.jscodeshift; 41 | 42 | let root = j(file.source); 43 | inlineRequire(root, j); 44 | root.find(j.VariableDeclaration).forEach(path => { 45 | try { 46 | path.value.declarations.map(decl => { 47 | if (decl.init.callee.name === "require") { 48 | if (decl.id.properties) { 49 | // const {A, B} = require(...); 50 | //console.log(decl.id.properties); 51 | // console.log(path.value.declarations[0].id.properties[0].value.name); 52 | const imports = []; 53 | path.value.declarations[0].id.properties.map(property => { 54 | const varName = property.key.name; 55 | let asName = property.value.name; 56 | if (!asName) { 57 | imports.push(varName); 58 | path.insertAfter("const {" + j(property.value.properties).toSource() + "} = " + varName + ";"); 59 | } else if (varName !== asName) { 60 | imports.push(varName + " as " + asName); 61 | } else { 62 | imports.push(j(property).toSource()); 63 | } 64 | }); 65 | path.insertBefore( 66 | "import {" + imports.join(", ") + "} from '" + path.value.declarations[0].init.arguments[0].value + "';" 67 | ); 68 | } else { 69 | const val = j(path.value.declarations[0].id).toSource(); 70 | let toImport = "* as " + val; 71 | if (path.value.declarations[0].id) { 72 | const shouldDirectImport = directImport(path.value.declarations[0].id.name, root, j); 73 | 74 | if (shouldDirectImport) { 75 | toImport = val; 76 | } 77 | } 78 | const module = path.value.declarations[0].init.arguments[0].value; 79 | if (!module.startsWith(".")) { 80 | toImport = val; 81 | } 82 | 83 | path.insertBefore("import " + toImport + " from '" + module + "';"); 84 | } 85 | } else if (decl.init.callee.callee.name === "require") { 86 | // const A = require(x)(y) => 87 | // import AModule from x; 88 | // const A = x(y); 89 | const moduleName = decl.init.callee.arguments[0].value; 90 | const arg = j(decl.init.arguments).toSource(); 91 | const varName = decl.id.name; 92 | const importModuleName = varName + "Module"; 93 | path.insertBefore("import " + importModuleName + " from '" + moduleName + "';"); 94 | path.insertBefore("const " + varName + " = " + importModuleName + "(" + arg + ");"); 95 | } 96 | }); 97 | //const decl = path.value.declarations[0]; 98 | 99 | path.replace(""); 100 | } catch (e) { 101 | } 102 | }); 103 | 104 | return root.toSource(); 105 | }; 106 | 107 | -------------------------------------------------------------------------------- /fix_import_star.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const findPath = (filePath, importPath) => { 4 | const filePathArr = filePath.split('/'); 5 | filePathArr.pop(); 6 | const importPathArr = importPath.split('/'); 7 | while (importPathArr[0] === '..') { 8 | filePathArr.pop(); 9 | importPathArr.shift(); 10 | } 11 | if (importPathArr[0] === '.') { 12 | importPathArr.shift(); 13 | } 14 | console.log('filePathArr', filePathArr); 15 | 16 | return filePathArr.concat(importPathArr).join('/'); 17 | }; 18 | /** 19 | * If we see import * as A from 'path/to/file'. We will read 20 | * path/to/file and see if there is export default there. 21 | */ 22 | export default (file, api, options) => { 23 | console.log('start'); 24 | const j = api.jscodeshift; 25 | 26 | const root = j(file.source); 27 | root.find(j.ImportDeclaration).forEach(path => { 28 | try { 29 | if (path.value.specifiers[0].type !== 'ImportNamespaceSpecifier') { 30 | return; 31 | } 32 | const filePath = file.path; 33 | const importPath = path.value.source.value; 34 | let importFilePath = findPath(filePath, importPath); 35 | let isDir = false; 36 | try { 37 | isDir = fs.lstatSync(importFilePath).isDirectory(); 38 | if (isDir) { 39 | importFilePath += '/index.ts'; 40 | } 41 | } catch (e) {} 42 | if (!isDir) { 43 | importFilePath = importFilePath + '.ts'; 44 | } 45 | if (!fs.existsSync(importFilePath)) { 46 | console.log('file not exists: ', importFilePath); 47 | return; 48 | } 49 | let isExportDefault = false; 50 | const content = fs.readFileSync(importFilePath, 'utf8'); 51 | console.log('content', content); 52 | for (const line of content.split('\n')) { 53 | if (line.startsWith('export default ')) { 54 | console.log('set true'); 55 | isExportDefault = true; 56 | } 57 | } 58 | console.log('isExportDefault', isExportDefault); 59 | if (isExportDefault) { 60 | path.replace( 61 | 'import ' + 62 | path.value.specifiers[0].local.name + 63 | " from '" + 64 | importPath + 65 | "';", 66 | ); 67 | } 68 | } catch (e) { 69 | console.log(e); 70 | } 71 | }); 72 | return root.toSource(); 73 | }; 74 | -------------------------------------------------------------------------------- /fix_node_module.ts: -------------------------------------------------------------------------------- 1 | import core = require('jscodeshift'); 2 | 3 | 'use strict'; 4 | exports.__esModule = true; 5 | let printExport = function(name, value, j) { 6 | if (name === 'delete') { 7 | name = '__need_fix_delete'; 8 | } 9 | return 'export const ' + name + ' = ' + j(value).toSource() + ';'; 10 | }; 11 | let findDecl = function(varName, root, j) { 12 | let res; 13 | root.find(j.VariableDeclaration).forEach(function(path) { 14 | try { 15 | if (path.value.declarations[0].id.name === varName) { 16 | res = path; 17 | } 18 | } catch (e) {} 19 | }); 20 | return res; 21 | }; 22 | /** 23 | * module.exports { A: ..., } => export export const A = ...; 24 | * module.exports = ClassA => export default ClassA; 25 | * @param root 26 | * @param j 27 | */ 28 | let fixModuleExportObj = function(root, j) { 29 | root.find(j.ExpressionStatement).forEach(function(path) { 30 | try { 31 | let exp = path.value.expression; 32 | if ( 33 | exp.type !== 'AssignmentExpression' || 34 | exp.left.object.name !== 'module' || 35 | exp.left.property.name !== 'exports' 36 | ) { 37 | return; 38 | } 39 | if (exp.right.type === 'Identifier') { 40 | // module.exports = ClassA; 41 | path.insertBefore('export default ' + exp.right.name + ';'); 42 | } else if (exp.right.type === 'ArrowFunctionExpression') { 43 | path.insertBefore( 44 | 'export default ' + j(exp.right).toSource() + ';', 45 | ); 46 | } else { 47 | exp.right.properties.map(function(property) { 48 | if (property.type === 'SpreadElement') { 49 | // module.exports = { ...varA } 50 | root.find(j.VariableDeclarator).forEach(function(v) { 51 | try { 52 | if ( 53 | v.value.id.name !== property.argument.name 54 | ) { 55 | return; 56 | } 57 | if (v.value.init.properties) { 58 | v.value.init.properties.map(function(p) { 59 | path.insertBefore( 60 | printExport(p.key.name, p.value, j), 61 | ); 62 | }); 63 | } else if ( 64 | v.value.init.callee.name === 'require' 65 | ) { 66 | path.insertBefore( 67 | 'export * from "' + 68 | v.value.init.arguments[0].value + 69 | '";', 70 | ); 71 | } else { 72 | console.log('problem with ', v); 73 | } 74 | } catch (e) {} 75 | }); 76 | } else { 77 | if ( 78 | property.key.type === 'Identifier' && 79 | property.key.type === property.value.type 80 | ) { 81 | // const A = 'hi'; 82 | // module.exports = { A } 83 | let p = findDecl(property.value.name, root, j); 84 | if (p) { 85 | path.insertBefore( 86 | 'export { ' + property.value.name + ' };', 87 | ); 88 | } 89 | } else { 90 | path.insertBefore( 91 | printExport( 92 | property.key.name, 93 | property.value, 94 | j, 95 | ), 96 | ); 97 | } 98 | } 99 | }); 100 | } 101 | path.replace(''); 102 | } catch (e) {} 103 | }); 104 | }; 105 | /** 106 | * fix module.export.A = ... to export const A = ... 107 | * @param root 108 | * @param j 109 | */ 110 | let fixModuleExportIdent = function(root, j) { 111 | root.find(j.ExpressionStatement).forEach(function(path) { 112 | try { 113 | let obj = path.value.expression.left.object; 114 | if ( 115 | obj.object.name !== 'module' || 116 | obj.property.name !== 'exports' 117 | ) { 118 | return; 119 | } 120 | path.insertBefore( 121 | printExport( 122 | path.value.expression.left.property.name, 123 | path.value.expression.right, 124 | j, 125 | ), 126 | ); 127 | path.replace(''); 128 | } catch (e) {} 129 | }); 130 | }; 131 | /** 132 | * module.export.A = ... 133 | * const b = module.export.A; 134 | * 135 | * will be converted to 136 | * 137 | * const b = A; 138 | * @param root 139 | * @param j 140 | */ 141 | let fixUseModuleExport = function(root, j) { 142 | root.find(j.MemberExpression).forEach(function(path) { 143 | try { 144 | if ( 145 | path.value.object.object.name !== 'module' || 146 | path.value.object.property.name !== 'exports' 147 | ) { 148 | return; 149 | } 150 | path.replace(path.value.property); 151 | } catch (e) {} 152 | }); 153 | }; 154 | exports['default'] = (function (file, api, options) { 155 | let j = api.jscodeshift; 156 | let root = j(file.source); 157 | fixModuleExportObj(root, j); 158 | root = j(root.toSource()); 159 | fixModuleExportIdent(root, j); 160 | root = j(root.toSource()); 161 | fixUseModuleExport(root, j); 162 | return root.toSource(); 163 | }; 164 | -------------------------------------------------------------------------------- /fix_ts_ignore_in_jsx.js: -------------------------------------------------------------------------------- 1 | export default (file, api) => { 2 | const j = api.jscodeshift; 3 | 4 | const root = j(file.source); 5 | const process = path => { 6 | for (const child of path.node.children) { 7 | if ( 8 | child.type === 'JSXText' && 9 | child.value.includes('@ts-ignore') 10 | ) { 11 | const indent = ' '.repeat(child.indent); 12 | child.value = `\n${indent}{/*\n${indent} // @ts-ignore */}\n${' '.repeat( 13 | child.indent, 14 | )}`; 15 | } 16 | } 17 | }; 18 | // root.find(j.JSXElement).forEach(path => { 19 | root.find(j.JSXFragment).forEach(path => { 20 | process(path); 21 | }); 22 | root.find(j.JSXElement).forEach(path => { 23 | process(path); 24 | }); 25 | return root.toSource(); 26 | }; 27 | 28 | // module.exports.parser = 'tsx'; 29 | --------------------------------------------------------------------------------