known-ui
Version:
A CLI tool for integrating Known UI components into your Next.js projects.
165 lines (140 loc) • 4.72 kB
JavaScript
import { transform } from "@babel/core";
import * as parser from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
import * as t from "@babel/types";
async function convertTsxToJsx(content) {
try {
// First pass: Use Babel to handle basic TypeScript transformations
const result = await transform(content, {
plugins: [
"@babel/plugin-transform-typescript",
["@babel/plugin-syntax-typescript", { isTSX: true }],
],
presets: ["@babel/preset-react"],
retainLines: true,
});
if (!result?.code) {
throw new Error("Babel transformation failed");
}
// Parse the transformed code
const ast = parser.parse(result.code, {
sourceType: "module",
plugins: ["jsx", "react"],
});
// Second pass: Custom AST transformations
traverse(ast, {
// Clean up React.forwardRef type annotations
CallExpression(path) {
if (
t.isMemberExpression(path.node.callee) &&
t.isIdentifier(path.node.callee.object, { name: "React" }) &&
t.isIdentifier(path.node.callee.property, { name: "forwardRef" })
) {
path.node.typeParameters = null;
}
},
// Remove remaining type imports
ImportDeclaration(path) {
if (path.node.importKind === "type") {
path.remove();
}
},
// Clean up function parameters
Function(path) {
path.node.params.forEach((param) => {
if (t.isIdentifier(param)) {
param.typeAnnotation = null;
} else if (t.isObjectPattern(param)) {
param.typeAnnotation = null;
param.properties.forEach((prop) => {
if (t.isObjectProperty(prop)) {
prop.typeAnnotation = null;
}
});
}
});
},
// Remove type assertions
TSAsExpression(path) {
path.replaceWith(path.node.expression);
},
// Clean up variable declarations
VariableDeclarator(path) {
path.node.id.typeAnnotation = null;
},
});
// Generate the final code
const output = generate(ast, {
retainLines: true,
quotes: "single",
compact: false,
});
// Post-processing cleanup
let finalCode = output.code
// Remove any remaining type annotations
.replace(/:\s*[A-Za-z][\w\s<>[\],{}|&.]*(?=[,)])/g, "")
// Clean up empty lines
.replace(/\n\s*\n\s*\n/g, "\n\n")
// Ensure proper component displayName
.replace(/(\w+)\.displayName\s*=\s*(['"])\1\2/g, '$1.displayName = "$1"')
// Fix import statements
.replace(/import\s*{\s*(\w+)\s*}\s*from\s*['"]@\/types.*?['"]/g, "")
// Remove empty interfaces
.replace(/interface\s+\w+\s*\{\s*\}\s*/g, "");
// Format the code with proper indentation
finalCode = formatCode(finalCode);
return finalCode;
} catch (error) {
console.error("Error converting TSX to JSX:", error);
// Fallback to the original content if conversion fails
return content;
}
}
// Improved code formatting function
function formatCode(code) {
const lines = code.split("\n");
let indent = 0;
const formattedLines = [];
let inJSX = false;
let inImports = false;
for (let i = 0; i < lines.length; i++) {
let line = lines[i].trim();
if (!line) {
// Preserve empty lines between logical blocks
if (
formattedLines.length &&
formattedLines[formattedLines.length - 1] !== ""
) {
formattedLines.push("");
}
continue;
}
// Handle imports grouping
if (line.startsWith("import")) {
inImports = true;
} else if (inImports) {
inImports = false;
formattedLines.push("");
}
// Track JSX and brackets for proper indentation
if (line.includes("=>") && line.includes("(") && !line.includes(")")) {
inJSX = true;
}
// Adjust indent based on brackets
const openBrackets = (line.match(/[{(]/g) || []).length;
const closeBrackets = (line.match(/[})]/g) || []).length;
// Calculate proper indentation
const currentIndent = " ".repeat(Math.max(0, indent));
// Add the formatted line
formattedLines.push(currentIndent + line);
// Update indent for next line
indent += openBrackets - closeBrackets;
// Handle JSX closing
if (inJSX && line.includes(")")) {
inJSX = false;
}
}
return formattedLines.join("\n");
}
export { convertTsxToJsx };