js2flowchart
Version:
> Why? While I've been working on [Under-the-hood-ReactJS](https://github.com/Bogdan-Lyashenko/Under-the-hood-ReactJS) I spent enormous amount of time on creating schemes. Each change in code or flowchart affects all entire scheme instantly, forcing you t
311 lines (246 loc) • 9.02 kB
JavaScript
import generate from 'babel-generator';
import { TOKEN_TYPES, CLASS_FUNCTION_KINDS } from 'shared/constants';
export const idleConverter = path => {
return generate(path.node).code;
};
export const identifierConverter = path => {
if (path.parent.type === TOKEN_TYPES.SPREAD_PROPERTY) {
return '...' + idleConverter(path);
}
return idleConverter(path);
};
/* function */
export const functionConverter = path => {
const node = path.node,
paramsCode = getFunctionParametersCode(node.params);
let name = '';
if (node.id) {
name = getAnonymousFunctionName(path) + 'function ' + node.id.name + paramsCode;
} else if (node.type === TOKEN_TYPES.ARROW_FUNCTION_EXPRESSION) {
name = getAnonymousFunctionName(path) + paramsCode + ' =>';
} else if (node.type === TOKEN_TYPES.CLASS_METHOD || node.type === TOKEN_TYPES.OBJECT_METHOD) {
name =
node.kind === CLASS_FUNCTION_KINDS.CONSTRUCTOR
? 'constructor' + paramsCode
: node.key.name + paramsCode;
} else {
name = getAnonymousFunctionName(path) + 'function' + paramsCode;
}
return { name, pathParentType: path.parent.type };
};
export const getAnonymousFunctionName = path => {
const parent = path.parent;
if (
!parent ||
(parent.type !== TOKEN_TYPES.VARIABLE_DECLARATOR &&
parent.type !== TOKEN_TYPES.ASSIGNMENT_EXPRESSION &&
parent.type !== TOKEN_TYPES.OBJECT_PROPERTY)
) {
return '';
}
if (parent.left) {
return generate(parent.left).code + ' = ';
}
const parentId = parent.id;
return parentId ? parentId.name + ' = ' : '';
};
export const getFunctionParametersCode = params => {
return `(${params
.map(p => {
if (p.name) {
return p.name;
}
return generate(p).code;
})
.join(', ')})`;
};
export const returnConverter = path => {
const node = path.node;
if (
node.argument && (
[TOKEN_TYPES.CONDITIONAL_EXPRESSION, TOKEN_TYPES.OBJECT_EXPRESSION].includes(
node.argument.type
) ||
isFunctionType(node.argument.type))
) {
return 'return';
}
return path.node.argument ? `return ${generate(path.node.argument).code}` : 'return';
};
/* end function */
/* loop */
export const loopConverter = ({ node }) => {
if (node.test) {
return generate(node.test).code;
}
if (node.left && node.right) {
const innerPart = node.type === TOKEN_TYPES.FOR_OF_STATEMENT ? 'of' : 'in';
const leftPart =
node.left.type === TOKEN_TYPES.VARIABLE_DECLARATION
? getVariableDeclarations(node.left.declarations)
: generate(node.left).code;
return `${leftPart} ${innerPart} ${generate(node.right).code}`;
}
};
export const continueConverter = path => {
return path.node.label ? `continue ${generate(path.node.label).code}` : 'continue';
};
/* end loop */
export const conditionalConverter = path => {
return `(${generate(path.node.test).code})`;
};
/* try-catch */
export const tryConverter = path => {
return `try`;
};
export const catchConverter = path => {
return path.node.param ? `catch (${generate(path.node.param).code})` : '*catchConverter*';
};
export const finallyConverter = path => {
//TODO: fix `finally`, not implemented yet because it presents only as a part of parent,
//there is no `finally` visitor as it exist for `catch`
//seems like to do that each try-catch block should be handled in a different way
return '*finallyConverter*';
};
/* end try-catch */
/* switch-case */
export const switchStatementConverter = path => {
return `switch (${generate(path.node.discriminant).code})`;
};
export const caseConverter = path => {
return path.node.test ? `case ${generate(path.node.test).code}:` : 'default:';
};
export const breakConverter = path => {
return path.node.label ? `break ${generate(path.node.label).code}:` : 'break';
};
/* end switch - case */
export const withStatementConverter = path => {
return `with (${generate(path.node.object).code})`;
};
export const programConverter = path => {
return `${path.node.type}: source ${path.node.sourceType}`;
};
export const throwStatementConverter = path => {
return `throw ${generate(path.node.argument).code}`;
};
export const debuggerConverter = path => {
return `debugger`;
};
export const getVariableDeclarations = variables =>
variables.map(v => variableDeclaratorConverter({ node: v })).join(', ');
export const variableDeclaratorConverter = path => {
const node = path.node,
parentKind = path.parent.kind;
if (isNodeContainsFunc(node.init) || node.init.type === TOKEN_TYPES.CONDITIONAL_EXPRESSION) {
return `${parentKind} ${node.id.name} = `;
}
let variableName = '';
if (node.id.type === TOKEN_TYPES.OBJECT_PATTERN) {
variableName = '{...}';
} else if (node.id.type === TOKEN_TYPES.ARRAY_PATTERN) {
variableName = '[...]';
} else {
variableName = node.id.name;
}
if (
node.init &&
[TOKEN_TYPES.CALL_EXPRESSION, TOKEN_TYPES.NEW_EXPRESSION].includes(node.init.type)
) {
return `${parentKind} ${variableName} = ` + callExpressionConverter({ node: node.init });
}
if (node.init && node.init.type === TOKEN_TYPES.OBJECT_EXPRESSION) {
return `${parentKind} ${variableName} = ${objectExpressionConverter()}`;
}
if (node.id && node.id.type === TOKEN_TYPES.OBJECT_PATTERN) {
return `${parentKind} {...} = ${node.init.name}`;
}
if (node.id && node.id.type === TOKEN_TYPES.ARRAY_PATTERN) {
return `${parentKind} [...] = ${node.init.name}`;
}
return parentKind + ' ' + generate(node).code;
};
export const assignmentExpressionConverter = ({ node }) => {
if (isNodeContainsFunc(node.right) || node.right.type === TOKEN_TYPES.CONDITIONAL_EXPRESSION) {
return `${getLeftAssignmentName(node.left)} ${node.operator} `;
}
if (node.right.type === TOKEN_TYPES.OBJECT_EXPRESSION) {
return `${getLeftAssignmentName(
node.left
)} ${node.operator} ${objectExpressionConverter()}`;
}
if ([TOKEN_TYPES.CALL_EXPRESSION, TOKEN_TYPES.NEW_EXPRESSION].includes(node.right.type)) {
return `${getLeftAssignmentName(node.left)} ${node.operator} ${callExpressionConverter({
node: node.right
})}`;
}
return generate(node).code;
};
const getLeftAssignmentName = node => {
if (node.name) {
return node.name;
}
return generate(node).code;
};
export const callExpressionConverter = ({ node }) => {
let argumentsCode = '';
if (node.arguments && node.arguments.length) {
argumentsCode = node.arguments.map(getArgumentName).join(', ');
}
const callee = node.callee;
if (
callee.type === TOKEN_TYPES.MEMBER_EXPRESSION &&
callee.object.type === TOKEN_TYPES.CALL_EXPRESSION
) {
return { name: `.${callee.property.name}(${argumentsCode})`, chain: true };
} else if (argumentsCode) {
return `${generate(node.callee).code}(${argumentsCode})`;
}
return generate(node).code;
};
const getArgumentName = argument => {
if (isNodeContainsFunc(argument)) return '*()';
if (argument.type === TOKEN_TYPES.OBJECT_EXPRESSION) return objectExpressionConverter();
if (argument.name) return argument.name;
if (argument.value)
return argument.type === TOKEN_TYPES.STRING_LITERAL
? `'${argument.value}'`
: argument.value;
return generate(argument).code;
};
export const objectExpressionConverter = path => {
const name = '{*}';
if (path) return { name, pathParentType: path.parent.type };
return name;
};
export const objectPropertyConverter = path => {
const node = path.node;
if (node.value && isFunctionType(node.value.type)) {
return node.key.name + ': ';
}
if (node.value && node.value.type === TOKEN_TYPES.OBJECT_EXPRESSION) {
return node.key.name + ': ' + objectExpressionConverter();
}
return generate(node).code;
};
const getFirstCallee = callee => {
if (!callee) return callee;
if (
callee.type === TOKEN_TYPES.MEMBER_EXPRESSION &&
callee.object.type === TOKEN_TYPES.CALL_EXPRESSION
) {
return getFirstCallee(callee.object);
}
return callee;
};
export const isFunctionType = type => {
return [
TOKEN_TYPES.FUNCTION_EXPRESSION,
TOKEN_TYPES.FUNCTION,
TOKEN_TYPES.ARROW_FUNCTION_EXPRESSION,
TOKEN_TYPES.FUNCTION_DECLARATION
].includes(type);
};
export const isNodeContainsFunc = node => {
const functions = [TOKEN_TYPES.ARROW_FUNCTION_EXPRESSION, TOKEN_TYPES.FUNCTION_EXPRESSION];
return node && functions.indexOf(node.type) !== -1;
};