Skip to content

Instantly share code, notes, and snippets.

@robertleeplummerjr
Created August 14, 2019 10:30
Show Gist options
  • Save robertleeplummerjr/95c725d1984a1584c4db3344d1da8c39 to your computer and use it in GitHub Desktop.
Save robertleeplummerjr/95c725d1984a1584c4db3344d1da8c39 to your computer and use it in GitHub Desktop.
A script that migrates from using assert to jest's expect
const { parse } = require('acorn');
const fs = require('fs');
const file = fs.readFileSync('./__tests__/file-name.js').toString();
const rootAST = parse(file, {
locations: true
});
function traverse(ast) {
if (Array.isArray(ast)) {
for (let i = 0; i < ast.length; i++) {
traverse(ast[i]);
}
return;
}
replaceMemberExpression(ast);
switch (ast.type) {
case 'Program':
traverse(ast.body);
break;
case 'FunctionDeclaration':
case 'FunctionExpression':
case 'ArrowFunctionExpression':
traverse(ast.params);
traverse(ast.body);
break;
case 'ReturnStatement':
if (ast.argument) {
traverse(ast.argument);
}
break;
case 'BinaryExpression':
case 'AssignmentExpression':
case 'LogicalExpression':
traverse(ast.left);
traverse(ast.right);
break;
case 'Literal':
case 'Identifier':
case 'ThisExpression':
case 'DebuggerStatement':
case 'ThrowStatement':
case 'TemplateElement':
case 'ContinueStatement':
break;
case 'ExpressionStatement':
traverse(ast.expression);
break;
case 'BlockStatement':
traverse(ast.body);
break;
case 'IfStatement':
traverse(ast.test);
traverse(ast.consequent);
if (ast.alternate) {
traverse(ast.alternate);
}
break;
case 'ForStatement':
traverse(ast.init);
traverse(ast.test);
traverse(ast.update);
traverse(ast.body);
break;
case 'ForInStatement':
traverse(ast.left);
traverse(ast.right);
traverse(ast.body);
break;
case 'VariableDeclaration':
traverse(ast.declarations);
break;
case 'VariableDeclarator':
if (ast.init) {
traverse(ast.init);
}
break;
case 'UnaryExpression':
case 'UpdateExpression':
traverse(ast.argument);
break;
case 'MemberExpression':
traverse(ast.object);
traverse(ast.property);
break;
case 'ArrayExpression': return traverse(ast.elements);
case 'ConditionalExpression':
traverse(ast.test);
traverse(ast.consequent);
traverse(ast.alternate);
break;
case 'CallExpression':
case 'NewExpression':
traverse(ast.callee);
traverse(ast.arguments);
break;
case 'ObjectExpression': return traverse(ast.properties);
case 'Property':
traverse(ast.key);
traverse(ast.value);
break;
case 'TemplateLiteral':
traverse(ast.expressions);
traverse(ast.quasis);
break;
default:
throw new Error(`Unknown ast type : ${ ast.type }`);
}
}
const replacements = [];
function replaceMemberExpression(ast) {
if (
ast
&& ast.type === 'CallExpression'
&& ast.callee
&& ast.callee.type === 'MemberExpression'
&& ast.callee.object
&& ast.callee.object.type === 'Identifier'
&& ast.callee.object.name === 'assert'
) {
switch (ast.callee.property.name) {
case 'equal': {
replace(ast, `expect(${getAstString(file, ast.arguments[0])}).toBe(${getAstString(file, ast.arguments[1])})`);
break;
}
case 'deepEqual': {
replace(ast, `expect(${getAstString(file, ast.arguments[0])}).toEqual(${getAstString(file, ast.arguments[1])})`);
break;
}
case 'throws':
replace(ast, `expect(${getAstString(file, ast.arguments[0])}).toThrow()`);
break;
case 'doesNotThrow':
replace(ast, `expect(${getAstString(file, ast.arguments[0])}).not.toThrow()`);
break;
case 'notEqual':
replace(ast, `expect(${getAstString(file, ast.arguments[0])}).not.toBe(${getAstString(file, ast.arguments[1])})`);
break;
case 'notDeepEqual':
replace(ast, `expect(${getAstString(file, ast.arguments[0])}).not.toEqual(${getAstString(file, ast.arguments[1])})`);
break;
case 'ok':
replace(ast, `expect(${getAstString(file, ast.arguments[0])}).toBeTruthy()`);
break;
default:
console.log(`unhandled ${ ast.callee.property.name }`);
}
} else if (
ast
&& ast.type === 'CallExpression'
&& ast.callee
&& ast.callee.name === 'assert'
) {
replace(ast, `expect(${getAstString(file, ast.arguments[0])}).toBeTruthy()`);
}
}
const logOriginals = false;
const logReplacements = false;
function replace(ast, replacement) {
if (ast.arguments.length > 2) {
console.warn(`verbiage "${getAstString(file, ast.arguments[2])}" lost`);
}
const original = getAstString(file, ast);
if (file.indexOf(original) === -1) {
getAstString(file, ast);
throw new Error('there is a bug with statement: ' + original);
}
replacements.push({
start: ast.start,
end: ast.end,
replacement,
original,
});
if (logOriginals) {
console.log(original);
}
if (logReplacements) {
console.log(replacement);
}
}
function getAstString(source, ast) {
const lines = Array.isArray(source) ? source : source.split(/\r?\n/g);
const start = ast.loc.start;
const end = ast.loc.end;
const result = [];
if (start.line === end.line) {
result.push(lines[start.line - 1].substring(start.column, end.column));
} else {
result.push(lines[start.line - 1].slice(start.column));
for (let i = start.line; i < end.line - 1; i++) {
result.push(lines[i]);
}
result.push(lines[end.line - 1].slice(0, end.column));
}
return result.join('\n');
}
traverse(rootAST);
let fileCopy = file;
for (let i = 0; i < replacements.length; i++) {
fileCopy = fileCopy.split(replacements[i].original).join(replacements[i].replacement);
}
fs.writeFileSync('./__tests__/file-name.expect.js', fileCopy);
@robertleeplummerjr
Copy link
Author

I know I'll probably use it in the future.

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