Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 80 additions & 80 deletions codemods/define-properties/index.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,14 @@
import jscodeshift from 'jscodeshift';
import {
DEFAULT_IMPORT,
getImportIdentifierMap,
getVariableExpressionHasIdentifier,
insertAfterImports,
insertCommentAboveNode,
removeImport,
replaceRequireMemberExpression,
} from '../shared.js';
import { ts } from '@ast-grep/napi';
import { findDefaultImportIdentifier } from '../shared-ast-grep.js';

/**
* @typedef {import('../../types.js').Codemod} Codemod
* @typedef {import('../../types.js').CodemodOptions} CodemodOptions
*/
const MODULE_NAME = 'define-properties';

/**
*
* @param {string} name
* @returns
* @returns {string}
*/
const definePropertiesTemplate = (name) => `
const ${name} = function (object, map) {
const definePropertiesTemplate = (name) =>
`const ${name} = function (object, map) {
let propKeys = Object.keys(map);
propKeys = propKeys.concat(Object.getOwnPropertySymbols(map));

Expand All @@ -43,95 +31,107 @@ const ${name} = function (object, map) {
return object;
};`;

/**
* @typedef {import('../../types.js').Codemod} Codemod
* @typedef {import('../../types.js').CodemodOptions} CodemodOptions
*/

/**
* @param {CodemodOptions} [options]
* @returns {Codemod}
*/
export default function (options) {
return {
name: 'define-properties',
name: MODULE_NAME,
to: 'native',
transform: ({ file }) => {
const j = jscodeshift;
const root = j(file.source);
const variableExpressionHasIdentifier =
getVariableExpressionHasIdentifier(
'define-properties',
'supportsDescriptors',
root,
j,
);
const root = ts.parse(file.source).root();
const edits = [];

const memberExprs = root.findAll({
rule: {
pattern: {
context: "require('define-properties').supportsDescriptors",
strictness: 'relaxed',
},
},
});

// Use case 1: require('define-properties').supportsDescriptors
if (variableExpressionHasIdentifier) {
const didReplace = replaceRequireMemberExpression(
'define-properties',
true,
root,
j,
);
return didReplace ? root.toSource(options) : file.source;
if (memberExprs.length > 0) {
for (const expr of memberExprs) {
edits.push(expr.replace('true'));
}
return root.commitEdits(edits);
}

const map = getImportIdentifierMap('define-properties', root, j);

const identifier = map[DEFAULT_IMPORT];
const { imports, identifierName } = findDefaultImportIdentifier(
root,
MODULE_NAME,
);
if (!identifierName) return file.source;

const callExpressions = root.find(j.CallExpression, {
callee: {
name: identifier,
},
const calls = root.findAll({
rule: { pattern: `${identifierName}($$$ARGS)` },
});

if (!callExpressions.length) {
removeImport('define-properties', root, j);
return root.toSource(options);
if (calls.length === 0) {
for (const imp of imports) {
edits.push(imp.replace(''));
}
return root.commitEdits(edits);
}

let transformCount = 0;
let dirty = false;

callExpressions.forEach((path) => {
const node = path.node;
const newIdentifier = `$${identifier}`;

// Use case 2: define(object, map);
if (node.arguments.length === 2) {
if (transformCount === 0) {
const defineFunction = definePropertiesTemplate(newIdentifier);
insertAfterImports(defineFunction, root, j);
}

// Not all call expressions have a name property, but node.callee should be of type Identifi
if ('name' in node.callee) {
node.callee.name = newIdentifier;
}
for (const call of calls) {
const args = (call.getMultipleMatches('ARGS') || []).filter(
(m) => m.kind() !== ',',
);

if (args.length === 2) {
const fn = call.field('function');
if (fn) edits.push(fn.replace(`$${identifierName}`));
transformCount++;
dirty = true;
}

// Use case 3: define(object, map, predicates);
if (node.arguments.length === 3) {
const comment = j.commentBlock(
'\n This usage of `define-properties` usage can be cleaned up through a mix of Object.defineProperty() and a custom predicate function.\n details can be found here: https://github.com/es-tooling/module-replacements-codemods/issues/66 \n',
true,
false,
);

const startLine = node.loc?.start.line ?? 0;
if (args.length === 3) {
let stmt = call;
while (stmt) {
const parent = stmt.parent();
if (!parent) break;
const k = parent.kind();
if (
k === 'expression_statement' ||
k === 'lexical_declaration' ||
k === 'variable_declaration'
) {
stmt = parent;
break;
}
stmt = parent;
}
const comment =
'/*\n This usage of `define-properties` usage can be cleaned up through a mix of Object.defineProperty() and a custom predicate function.\n details can be found here: https://github.com/es-tooling/module-replacements-codemods/issues/66 \n*/';
edits.push(stmt.replace(`${comment}\n${stmt.text()}`));
}
}

insertCommentAboveNode(comment, startLine, root, j);
if (transformCount === 0) {
return edits.length > 0 ? root.commitEdits(edits) : file.source;
}

dirty = true;
}
});
const newName = `$${identifierName}`;
const polyfill = definePropertiesTemplate(newName);
const allTransformed = transformCount === calls.length;

if (transformCount === callExpressions.length) {
removeImport('define-properties', root, j);
if (allTransformed) {
for (const imp of imports) edits.push(imp.replace(polyfill));
} else {
for (const imp of imports)
edits.push(imp.replace(`${imp.text()}\n\n${polyfill}`));
}

return dirty ? root.toSource(options) : file.source;
return edits.length > 0 ? root.commitEdits(edits) : file.source;
},
};
}
4 changes: 1 addition & 3 deletions test/fixtures/define-properties/case-2/after.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
const assert = require('assert');


const $define = function (object, map) {
let propKeys = Object.keys(map);
propKeys = propKeys.concat(Object.getOwnPropertySymbols(map));
Expand All @@ -23,6 +20,7 @@ const $define = function (object, map) {

return object;
};
const assert = require('assert');

const object1 = { a: 1, b: 2 };

Expand Down
4 changes: 1 addition & 3 deletions test/fixtures/define-properties/case-2/result.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
const assert = require('assert');


const $define = function (object, map) {
let propKeys = Object.keys(map);
propKeys = propKeys.concat(Object.getOwnPropertySymbols(map));
Expand All @@ -23,6 +20,7 @@ const $define = function (object, map) {

return object;
};
const assert = require('assert');

const object1 = { a: 1, b: 2 };

Expand Down
5 changes: 5 additions & 0 deletions test/fixtures/define-properties/case-5/after.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const sup = true;

if (sup) {
console.log("supports descriptors");
}
5 changes: 5 additions & 0 deletions test/fixtures/define-properties/case-5/before.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const sup = require("define-properties").supportsDescriptors;

if (sup) {
console.log("supports descriptors");
}
5 changes: 5 additions & 0 deletions test/fixtures/define-properties/case-5/result.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const sup = true;

if (sup) {
console.log("supports descriptors");
}
4 changes: 4 additions & 0 deletions types/codemods/define-properties/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* @typedef {import('../../types.js').Codemod} Codemod
* @typedef {import('../../types.js').CodemodOptions} CodemodOptions
*/
/**
* @param {CodemodOptions} [options]
* @returns {Codemod}
Expand Down