Skip to content

Instantly share code, notes, and snippets.

@CodeWitchBella
Last active February 7, 2022 17:30
Show Gist options
  • Save CodeWitchBella/3a98f1b9218a5b6e0775c181b4bccdab to your computer and use it in GitHub Desktop.
Save CodeWitchBella/3a98f1b9218a5b6e0775c181b4bccdab to your computer and use it in GitHub Desktop.
Patched create-element-to-jsx
/**
* Copyright 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
'use strict';
module.exports = function(file, api, options) {
const j = api.jscodeshift;
const printOptions = options.printOptions || {};
const root = j(file.source);
const ReactUtils = require('./utils/ReactUtils')(j);
const encodeJSXTextValue = value =>
value.replace(/</g, '&lt;').replace(/>/g, '&gt;');
const canLiteralBePropString = node =>{
return (node.raw || node.extra.raw).indexOf('\\') === -1 && node.value.indexOf('"') === -1
};
const isLiteralType = type => type === 'Literal' || type ==='StringLiteral' || type === 'NullLiteral'
const convertExpressionToJSXAttributes = expression => {
if (!expression) {
return {
attributes: [],
extraComments: []
};
}
const isReactSpread =
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.object.name === 'React' &&
expression.callee.property.name === '__spread';
const isObjectAssign =
expression.type === 'CallExpression' &&
expression.callee.type === 'MemberExpression' &&
expression.callee.object.name === 'Object' &&
expression.callee.property.name === 'assign';
const validSpreadTypes = [
'Identifier',
'MemberExpression',
'CallExpression'
];
if (isReactSpread || isObjectAssign) {
console.log('a')
const resultAttributes = [];
const resultExtraComments = expression.comments || [];
const { callee } = expression;
for (const node of [callee, callee.object, callee.property]) {
resultExtraComments.push(...(node.comments || []));
}
expression.arguments.forEach(expression => {
const { attributes, extraComments } = convertExpressionToJSXAttributes(
expression
);
resultAttributes.push(...attributes);
resultExtraComments.push(...extraComments);
});
return {
attributes: resultAttributes,
extraComments: resultExtraComments
};
} else if (validSpreadTypes.indexOf(expression.type) != -1) {
console.log('b')
return {
attributes: [j.jsxSpreadAttribute(expression)],
extraComments: []
};
} else if (expression.type === 'ObjectExpression') {
console.log('c')
const attributes = expression.properties.map(property => {
if (property.type === 'SpreadProperty') {
const spreadAttribute = j.jsxSpreadAttribute(property.argument);
spreadAttribute.comments = property.comments;
return spreadAttribute;
} else if (property.type === 'Property' || property.type === 'ObjectProperty') {
console.log(property)
const propertyValueType = property.value.type;
let value;
if (
isLiteralType(propertyValueType) &&
typeof property.value.value === 'string' &&
canLiteralBePropString(property.value)
) {
value = j.literal(property.value.value);
value.comments = property.value.comments;
} else {
value = j.jsxExpressionContainer(property.value);
}
let jsxIdentifier;
if (isLiteralType(property.key.type)) {
jsxIdentifier = j.jsxIdentifier(property.key.value);
} else {
jsxIdentifier = j.jsxIdentifier(property.key.name);
}
jsxIdentifier.comments = property.key.comments;
const jsxAttribute = j.jsxAttribute(jsxIdentifier, value);
jsxAttribute.comments = property.comments;
return jsxAttribute;
}
return null;
});
return {
attributes,
extraComments: expression.comments || []
};
} else if (isLiteralType(expression.type) && expression.value === null) {
console.log('d')
return {
attributes: [],
extraComments: expression.comments || []
};
} else {
throw new Error(`Unexpected attribute of type "${expression.type}"`);
}
};
const canConvertToJSXIdentifier = node =>
(isLiteralType(node.type) && typeof node.value === 'string') ||
node.type === 'Identifier' ||
(node.type === 'MemberExpression' &&
!node.computed &&
canConvertToJSXIdentifier(node.object) &&
canConvertToJSXIdentifier(node.property));
const jsxIdentifierFor = node => {
let identifier;
let comments = node.comments || [];
if (isLiteralType(node.type)) {
identifier = j.jsxIdentifier(node.value);
} else if (node.type === 'MemberExpression') {
let {
identifier: objectIdentifier,
comments: objectComments
} = jsxIdentifierFor(node.object);
let {
identifier: propertyIdentifier,
comments: propertyComments
} = jsxIdentifierFor(node.property);
identifier = j.jsxMemberExpression(objectIdentifier, propertyIdentifier);
comments.push(...objectComments, ...propertyComments);
} else {
identifier = j.jsxIdentifier(node.name);
}
return { identifier, comments };
};
const isCapitalizationInvalid = node =>
(isLiteralType(node.type) && !/^[a-z]/.test(node.value)) ||
(node.type === 'Identifier' && /^[a-z]/.test(node.name));
const convertNodeToJSX = node => {
const comments = node.value.comments || [];
const { callee } = node.value;
for (const calleeNode of [callee, callee.object, callee.property]) {
for (const comment of calleeNode.comments || []) {
comment.leading = true;
comment.trailing = false;
comments.push(comment);
}
}
const args = node.value.arguments;
if (
isCapitalizationInvalid(args[0]) ||
!canConvertToJSXIdentifier(args[0])
) {
return node.value;
}
const {
identifier: jsxIdentifier,
comments: identifierComments
} = jsxIdentifierFor(args[0]);
const props = args[1];
const { attributes, extraComments } = convertExpressionToJSXAttributes(
props
);
for (const comment of [...identifierComments, ...extraComments]) {
comment.leading = false;
comment.trailing = true;
comments.push(comment);
}
const children = args.slice(2).map((child, index) => {
if (
isLiteralType(child.type) &&
typeof child.value === 'string' &&
!child.comments &&
child.value !== '' &&
child.value.trim() === child.value
) {
return j.jsxText(encodeJSXTextValue(child.value));
} else if (
child.type === 'CallExpression' &&
child.callee.object &&
child.callee.object.name === 'React' &&
child.callee.property.name === 'createElement'
) {
const jsxChild = convertNodeToJSX(node.get('arguments', index + 2));
if (
jsxChild.type !== 'JSXElement' ||
(jsxChild.comments || []).length > 0
) {
return j.jsxExpressionContainer(jsxChild);
} else {
return jsxChild;
}
} else if (child.type === 'SpreadElement') {
return j.jsxExpressionContainer(child.argument);
} else {
return j.jsxExpressionContainer(child);
}
});
console.log(jsxIdentifier, attributes)
const openingElement = j.jsxOpeningElement(jsxIdentifier, attributes);
if (children.length) {
const endIdentifier = Object.assign({}, jsxIdentifier, { comments: [] });
// Add text newline nodes between elements so recast formats one child per
// line instead of all children on one line.
const paddedChildren = [j.jsxText('\n')];
for (const child of children) {
paddedChildren.push(child, j.jsxText('\n'));
}
const element = j.jsxElement(
openingElement,
j.jsxClosingElement(endIdentifier),
paddedChildren
);
element.comments = comments;
return element;
} else {
openingElement.selfClosing = true;
const element = j.jsxElement(openingElement);
element.comments = comments;
return element;
}
};
console.log(options)
if (options['explicit-require'] === false || ReactUtils.hasReact(root)) {
const mutations = root
.find(j.CallExpression, {
callee: {
object: {
name: 'React'
},
property: {
name: 'createElement'
}
}
})
.replaceWith(convertNodeToJSX)
.size();
if (mutations) {
return root.toSource(printOptions);
}
}
return null;
};

This is a version of create-element-to-jsx with modifications needed to convert typescript files with updated versions of various dependencies.

I used yarn to resolve jscodeshift version to 0.13.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment