Specialized utilities for working with React components and JSX syntax. These functions help identify React-specific patterns and manipulate JSX elements during AST transformations.
Checks if a node represents a reference to React.Component for class component inheritance.
/**
* Check if node represents React.Component reference
* @param node - Node to check
* @returns Whether the node is React.Component member expression
*/
function isReactComponent(node: t.Node | null | undefined): boolean;Checks if a tag name is a valid HTML/DOM-compatible tag name.
/**
* Check if tag name is HTML/DOM compatible
* @param tagName - Tag name to validate
* @returns Whether the tag name is a standard HTML element
*/
function isCompatTag(tagName: string): boolean;Processes JSX element children, filtering and transforming them appropriately.
/**
* Build processed children array from JSX element
* @param node - JSX element to extract children from
* @returns Array of processed child nodes
*/
function buildChildren(node: t.JSXElement): t.Node[];All React utilities are available under the react namespace:
const react: {
isReactComponent: (node: t.Node | null | undefined) => boolean;
isCompatTag: (tagName: string) => boolean;
buildChildren: (node: t.JSXElement) => t.Node[];
};import * as t from "@babel/types";
// Class extending React.Component
const reactClass = t.classDeclaration(
t.identifier("MyComponent"),
t.memberExpression(t.identifier("React"), t.identifier("Component")),
t.classBody([])
);
// Check if extends React.Component
if (reactClass.superClass && t.react.isReactComponent(reactClass.superClass)) {
console.log("This is a React class component");
}
// Other inheritance patterns
const customBase = t.memberExpression(
t.identifier("MyLib"),
t.identifier("Component")
);
console.log(t.react.isReactComponent(customBase)); // false - not React.Component
// Direct React.Component reference
const reactComponent = t.memberExpression(
t.identifier("React"),
t.identifier("Component")
);
console.log(t.react.isReactComponent(reactComponent)); // true// Standard HTML tags
console.log(t.react.isCompatTag("div")); // true
console.log(t.react.isCompatTag("span")); // true
console.log(t.react.isCompatTag("button")); // true
console.log(t.react.isCompatTag("input")); // true
console.log(t.react.isCompatTag("img")); // true
// Custom components (capitalized)
console.log(t.react.isCompatTag("MyComponent")); // false
console.log(t.react.isCompatTag("Button")); // false
console.log(t.react.isCompatTag("CustomDiv")); // false
// Invalid/non-standard tags
console.log(t.react.isCompatTag("invalid-tag")); // false
console.log(t.react.isCompatTag("customElement")); // false
// Edge cases
console.log(t.react.isCompatTag("")); // false
console.log(t.react.isCompatTag("123")); // false// JSX element with mixed children
const jsxElement = t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier("div"), []),
t.jsxClosingElement(t.jsxIdentifier("div")),
[
t.jsxText("Hello "),
t.jsxExpressionContainer(t.identifier("name")),
t.jsxText("!"),
t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier("span"), []),
t.jsxClosingElement(t.jsxIdentifier("span")),
[t.jsxText("nested")]
)
]
);
// Process children
const children = t.react.buildChildren(jsxElement);
console.log(children.length); // Processed children count
console.log(children.map(child => child.type)); // Types of processed children// Transform class component to functional component
function transformToFunctional(classDecl: t.ClassDeclaration): t.FunctionDeclaration | null {
// Check if it's a React component
if (!classDecl.superClass || !t.react.isReactComponent(classDecl.superClass)) {
return null; // Not a React component
}
// Find render method
const renderMethod = classDecl.body.body.find(
(member): member is t.ClassMethod =>
t.isClassMethod(member) &&
t.isIdentifier(member.key) &&
member.key.name === "render"
);
if (!renderMethod) return null;
// Create functional component
return t.functionDeclaration(
classDecl.id,
[], // No props for this simple example
renderMethod.body
);
}
// Usage
const ReactComponent = t.classDeclaration(
t.identifier("Welcome"),
t.memberExpression(t.identifier("React"), t.identifier("Component")),
t.classBody([
t.classMethod(
"method",
t.identifier("render"),
[],
t.blockStatement([
t.returnStatement(
t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier("h1"), []),
t.jsxClosingElement(t.jsxIdentifier("h1")),
[t.jsxText("Hello World")]
)
)
])
)
])
);
const functionalComponent = transformToFunctional(ReactComponent);
if (functionalComponent) {
console.log("Transformed to functional component");
}// Transform JSX elements based on tag type
function transformJSXElement(element: t.JSXElement): t.JSXElement {
const openingElement = element.openingElement;
if (t.isJSXIdentifier(openingElement.name)) {
const tagName = openingElement.name.name;
if (t.react.isCompatTag(tagName)) {
// It's a DOM element - might add HTML-specific attributes
console.log(`Processing DOM element: ${tagName}`);
// Add data attributes for DOM elements
const dataAttr = t.jsxAttribute(
t.jsxIdentifier("data-component"),
t.stringLiteral("dom-element")
);
if (!openingElement.attributes.some(attr =>
t.isJSXAttribute(attr) &&
t.isJSXIdentifier(attr.name) &&
attr.name.name === "data-component"
)) {
openingElement.attributes.push(dataAttr);
}
} else {
// It's a custom component
console.log(`Processing custom component: ${tagName}`);
// Add component tracking
const componentAttr = t.jsxAttribute(
t.jsxIdentifier("data-custom"),
t.stringLiteral("true")
);
openingElement.attributes.push(componentAttr);
}
}
return element;
}// Process JSX children for transformation
function processJSXChildren(element: t.JSXElement) {
const processedChildren = t.react.buildChildren(element);
processedChildren.forEach((child, index) => {
if (t.isJSXText(child)) {
console.log(`Text node ${index}: "${child.value}"`);
} else if (t.isJSXExpressionContainer(child)) {
console.log(`Expression ${index}:`, child.expression.type);
} else if (t.isJSXElement(child)) {
console.log(`Nested element ${index}:`, child.openingElement.name);
// Recursively process nested elements
processJSXChildren(child);
}
});
}
// Example usage
const complexJSX = t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier("div"), []),
t.jsxClosingElement(t.jsxIdentifier("div")),
[
t.jsxText("Start "),
t.jsxExpressionContainer(
t.conditionalExpression(
t.identifier("condition"),
t.stringLiteral("true"),
t.stringLiteral("false")
)
),
t.jsxText(" end"),
t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier("Button"), []),
t.jsxClosingElement(t.jsxIdentifier("Button")),
[t.jsxText("Click me")]
)
]
);
processJSXChildren(complexJSX);// Detect various React patterns
function analyzeReactCode(node: t.Node) {
const patterns = {
classComponents: 0,
jsxElements: 0,
domElements: 0,
customComponents: 0
};
t.traverse(node, {
ClassDeclaration(path) {
if (path.node.superClass && t.react.isReactComponent(path.node.superClass)) {
patterns.classComponents++;
}
},
JSXElement(path) {
patterns.jsxElements++;
const openingElement = path.node.openingElement;
if (t.isJSXIdentifier(openingElement.name)) {
if (t.react.isCompatTag(openingElement.name.name)) {
patterns.domElements++;
} else {
patterns.customComponents++;
}
}
}
});
return patterns;
}
// Usage with a React component file
const reactCode = t.program([
// Class component
t.classDeclaration(
t.identifier("App"),
t.memberExpression(t.identifier("React"), t.identifier("Component")),
t.classBody([
t.classMethod(
"method",
t.identifier("render"),
[],
t.blockStatement([
t.returnStatement(
t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier("div"), []),
t.jsxClosingElement(t.jsxIdentifier("div")),
[
t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier("Header"), []),
t.jsxClosingElement(t.jsxIdentifier("Header")),
[]
),
t.jsxElement(
t.jsxOpeningElement(t.jsxIdentifier("p"), []),
t.jsxClosingElement(t.jsxIdentifier("p")),
[t.jsxText("Content")]
)
]
)
)
])
)
])
)
]);
const analysis = analyzeReactCode(reactCode);
console.log(analysis);
// { classComponents: 1, jsxElements: 3, domElements: 2, customComponents: 1 }// Optimize JSX elements by processing children
function optimizeJSXElement(element: t.JSXElement): t.JSXElement {
// Process children to remove empty text nodes and optimize
const optimizedChildren = t.react.buildChildren(element);
// Filter out empty text nodes
const filteredChildren = optimizedChildren.filter(child => {
if (t.isJSXText(child)) {
return child.value.trim().length > 0;
}
return true;
});
// Update element with optimized children
const optimized = t.cloneDeep(element);
optimized.children = filteredChildren;
return optimized;
}