jscodeshaft
Version:
Collection of more or less primitive helpers and abstractions for JSCodeShift, build for design system migrations and upgrades.
165 lines (139 loc) • 4.08 kB
JavaScript
// libs
const fs = require('fs');
const path = require('path');
const {faker} = require('@faker-js/faker');
const {toMatchFile} = require('jest-file-snapshot');
const {applyTransform} = require('jscodeshift/dist/testUtils');
// prep
expect.extend({toMatchFile});
/**
* A simple wrapper for the snapshot testing boilerplate, which helps
* determine the correct paths, read the files and perform the matching.
* @param {string} dirname
* @returns {{
* runSnapshotTest: function,
* readSource: (function(*): string),
* getProcessorScaffold: (function(*, *): {testWrapperNode: *, root: *, j: *})
* }}
*/
exports.initHelpers = (dirname) => {
const sourceDir = path.join(dirname, 'sources');
const snapshotDir = path.join(dirname, 'snapshots');
const getPath = (fileName, mode) => (
path.join(
(mode === 'source' ? sourceDir : snapshotDir),
fileName,
)
);
const readSource = (moduleName) => fs.readFileSync(
getPath(moduleName, 'source'),
'utf8',
);
const runSnapshotTest = ({title, moduleName, processor}) => {
it(title, () => {
const source = readSource(moduleName);
const output = applyTransform(
processor,
{parser: 'flow'},
{source},
);
// For some reason, something adds an extra line break at the
// end of the file. This is obviously a hack, but it doesn't
// affect the actual transformation. In production, teams would
// follow the codemod with some sort of ESLint run anyway, so
// these things would get polished.
expect(output + "\n").toMatchFile(
getPath(moduleName),
);
});
};
const getProcessorScaffold = (fileInfo, api) => {
const j = api.jscodeshift;
const root = j(fileInfo.source);
const testWrapperNode = root
.findJSXElements('TestWrapper')
.nodes()
.at(0);
return {j, root, testWrapperNode};
};
return {
readSource,
runSnapshotTest,
getProcessorScaffold,
};
};
/**
* This is a direct copy of jscodeshift's `applyTransform` function
* with the only exception of it returning an AST rather than a string
* @param {function} processor
* @param {object} options
* @param {object} input
* @param {object} testOptions
* @returns {*}
*/
exports.toAst = (
processor,
options = {parser: 'flow'},
input = {source: ''},
testOptions = {}
) => {
// Handle ES6 modules using default export for the transform
const transform = processor.default ? processor.default : processor;
// Jest resets the processor registry after each test, so we need to always get
// a fresh copy of jscodeshift on every test run.
let jscodeshift = require('jscodeshift/dist/core');
if (testOptions.parser || processor.parser) {
jscodeshift = jscodeshift.withParser(testOptions.parser || processor.parser);
}
return transform(
input,
{
jscodeshift,
stats: () => {},
},
options || {},
);
};
/**
* Simple collection of values that are not AST Nodes
* @type {*[]}
*/
exports.NON_NODES = [
null,
undefined,
faker.datatype.uuid(),
0,
faker.finance.amount(0.01),
[],
{},
/regex/s,
new Date(),
faker.datatype.array(2),
JSON.parse(faker.datatype.json()),
];
/**
* Simple helper to print out different human-readable values or types.
* To be used for labelling tests.
* @param {*} value
* @returns {string}
*/
exports.printType = value => {
if (value instanceof Date && !isNaN(value)) {
return 'Date object';
} else if (value instanceof RegExp) {
return 'RegExp';
} else if (typeof value === 'string') {
return value.length ? 'string' : 'empty string';
} else if (typeof value === 'number' && !Number.isNaN(value)) {
return value === 0 ? 'zero' : 'truthy number';
} else if (Array.isArray(value) && value.length > 0) {
return 'populated array';
} else if (
value !== null
&& typeof value === 'object'
&& Object.keys(value).length > 0
) {
return 'populated object';
}
return JSON.stringify(value);
};