Skip to content

Commit

Permalink
fix: have a local version of the exports patch
Browse files Browse the repository at this point in the history
  • Loading branch information
homer0 committed May 20, 2022
1 parent 998bf36 commit 6a12fa6
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 20 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ documentation/*
node_modules/*
docs/*
esm/*
src/5to6-codemod
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
},
"config": {
"cjs2esm": {
"ignore": ["5to6-codemod"],
"filesWithShebang": [
"src/bin.js"
]
Expand Down
259 changes: 259 additions & 0 deletions src/5to6-codemod/exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/**
* exports - Replace module.exports calls with es6 exports
*/

var util = require('5to6-codemod/utils/main');

module.exports = function (file, api, options) {
var j = api.jscodeshift;
var root = j(file.source);
var firstNode = root.find(j.Program).get('body', 0).node;

/**
* Move `module.exports.thing()` to `thing()`
*/
function exportsCall(p) {
var functionCall = j.callExpression(p.value.callee.property, p.value.arguments);
// console.log(util.toString(p), '=>', util.toString(functionCall));
functionCall.comments = p.comments;
j(p).replaceWith(functionCall);
}

/**
* Must be run before exportsToExport
* ```
* module.exports.foo = foo
* module.exports.bar = bar
* exports.baz = baz
* to
* export { foo, bar, baz }
* ```
*/
function MultiExportsToExport(paths) {
var specifiers = [];
var filteredPaths = paths.filter(function (p) {
if (p.parentPath.parentPath.name !== 'body') return false;

var isModuleExport =
p.get('left', 'object', 'object', 'name').value === 'module' &&
p.get('left', 'object', 'property', 'name').value === 'exports';
var isExport = p.get('left', 'object', 'name').value === 'exports';

if (!isModuleExport && !isExport) return false;

return p.value.left.property.name === p.value.right.name;
});
filteredPaths.forEach(function (p, i) {
// aggregate all specifiers
specifiers.push(
j.exportSpecifier.from({
exported: j.identifier(p.value.right.name),
local: j.identifier(p.value.right.name),
}),
);

// replace the last module.exports.*
if (i === filteredPaths.length - 1) {
j(p.parentPath).replaceWith(j.exportDeclaration(false, null, specifiers));
} else {
j(p.parentPath).remove();
}
});
}

/**
* Move `module.exports.thing` to `export const thing`
*/
function exportsToExport(p) {
var declator = j.variableDeclarator(
j.identifier(p.value.left.property.name),
p.value.right,
);
var declaration = j.variableDeclaration('const', [declator]);
var exportDecl = j.exportDeclaration(false, declaration);
// console.log('[module.]exports.thing', util.toString(p), util.toString(exportDecl));
exportDecl.comments = p.parentPath.value.comments;
j(p.parentPath).replaceWith(exportDecl);
}

/**
* Move exports = module.exports to export default
*/
function exportsAndModuleExportsToDefault(p) {
var exportDecl = j.exportDeclaration(true, p.value.right.right);
// console.log('exports = module.exports', util.toString(p.value.right.right), util.toString(exportDecl));
exportDecl.comments = p.parentPath.value.comments;
j(p.parentPath).replaceWith(exportDecl);
}

/**
* Move `module.exports` to `export default`
*/
function exportsToDefault(p) {
var exportDecl = j.exportDeclaration(true, p.value.right);
// console.log('module.exports', util.toString(p), util.toString(exportDecl));
exportDecl.comments = p.parentPath.value.comments;
j(p.parentPath).replaceWith(exportDecl);
}

// find module.exports.thing = thing...
// find exports.thing = thing...
MultiExportsToExport(
root.find(j.AssignmentExpression, {
operator: '=',
left: {
type: 'MemberExpression',
},
right: {
type: 'Identifier',
},
}),
);

// find module.exports.thing = something....
root
.find(j.AssignmentExpression, {
operator: '=',
left: {
type: 'MemberExpression',
object: {
type: 'MemberExpression',
object: {
name: 'module',
},
property: { name: 'exports' },
},
},
})
.filter(function (p) {
return p.parentPath.parentPath.name === 'body';
})
.forEach(exportsToExport);

// find var House = module.exports = something....
root
.find(j.AssignmentExpression, {
operator: '=',
left: {
object: { name: 'module' },
property: { name: 'exports' },
},
})
.filter(function (p) {
var isVar = p.parentPath.value.type === 'VariableDeclarator';
var isOnBody = p.parentPath.parentPath.parentPath.parentPath.name === 'body';
return isVar && isOnBody;
})
.forEach(function (p) {
var decl = j.variableDeclarator(p.parentPath.value.id, p.value.right);
var exportDecl = j.exportDeclaration(true, p.parentPath.value.id);
// console.log(util.toString(decl), '\n', util.toString(exportDecl));
j(p.parentPath).replaceWith(decl);
j(p.parentPath.parentPath.parentPath).insertAfter(exportDecl);
});

// find module.exports = something....
root
.find(j.AssignmentExpression, {
operator: '=',
left: {
object: { name: 'module' },
property: { name: 'exports' },
},
})
.filter(function (p) {
return p.parentPath.parentPath.name === 'body';
})
.forEach(exportsToDefault);

// find exports.thing = something....
root
.find(j.AssignmentExpression, {
operator: '=',
left: {
object: { name: 'exports' },
},
})
.filter(function (p) {
return p.parentPath.parentPath.name === 'body';
})
.forEach(exportsToExport);

// find exports = module.exports = something....
root
.find(j.AssignmentExpression, {
operator: '=',
left: {
name: 'exports',
},
right: {
type: 'AssignmentExpression',
operator: '=',
left: {
type: 'MemberExpression',
object: {
name: 'module',
},
property: {
name: 'exports',
},
},
},
})
.filter(function (p) {
return p.parentPath.parentPath.name === 'body';
})
.forEach(exportsAndModuleExportsToDefault);

// find exports.house()
root
.find(j.CallExpression, {
callee: {
type: 'MemberExpression',
object: { name: 'exports' },
},
})
.forEach(exportsCall);

// find module.exports.house()
root
.find(j.CallExpression, {
callee: {
type: 'MemberExpression',
object: {
type: 'MemberExpression',
object: { name: 'module' },
property: { name: 'exports' },
},
},
})
.forEach(exportsCall);

// find var House = module.exports something....
root
.find(j.AssignmentExpression, {
operator: '=',
left: {
type: 'MemberExpression',
object: {
type: 'MemberExpression',
object: {
name: 'module',
},
property: { name: 'exports' },
},
},
})
.filter(function (p) {
return p.parentPath.parentPath.name === 'body';
})
.forEach(exportsToExport);

// re-add comment to to the top if necessary
var firstNode2 = root.find(j.Program).get('body', 0).node;
if (firstNode !== firstNode2) {
firstNode2.comments = firstNode.leadingComments;
}

return root.toSource(util.getRecastConfig(options));
};
24 changes: 20 additions & 4 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@ const { log, findFile, getAbsPathInfo, requireModule } = require('./utils');
* this module does.
*
* @type {string}
* @ignore
*/
const CJS2ESM_TRANSFORMATION_NAME = '<cjs2esm>';
/**
* A list of 5to6-codemod transformations that are executed from local patches.
*
* @type {string[]}
* @ignore
*/
const CODEMOD_PATCHED_TRANSFORMATIONS = ['exports'];
/**
* This is called every time an unexpected error is thrown; it logs the error using the
* `log`
Expand Down Expand Up @@ -364,29 +372,37 @@ const transformOutput = async (files, options) => {

const cwd = process.cwd();
let filepathForResolve;
let canUsePatch = false;
if (codemodOptions.path) {
filepathForResolve = path.join(cwd, codemodOptions.path, fileForResolve);
} else {
canUsePatch = true;
filepathForResolve = require.resolve(
path.join('5to6-codemod', 'transforms', fileForResolve),
);
}

const codeModPath = path.dirname(filepathForResolve);
const transformations = codemodFiles.map((file, index) => {
if (index === fileForResolveIndex) {
return filepathForResolve;
}
if (file === CJS2ESM_TRANSFORMATION_NAME) {
return path.join(__dirname, 'transformer.js');
}

const fileWithExt = `${file}.js`;

if (canUsePatch && CODEMOD_PATCHED_TRANSFORMATIONS.includes(file)) {
return path.join(__dirname, '5to6-codemod', fileWithExt);
}

if (index === fileForResolveIndex) {
return filepathForResolve;
}

if (fileWithExt.startsWith('.')) {
return path.resolve(fileWithExt);
}

return path.join(codeModPath, `${file}.js`);
return path.join(codeModPath, fileWithExt);
});

log('yellow', `Transforming ${files.length} files...`);
Expand Down
Loading

0 comments on commit 6a12fa6

Please sign in to comment.