Skip to content
Open
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
2 changes: 1 addition & 1 deletion recipes/cjs-to-esm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Codemod to assist CommonJS -> ESM migrations (imports, exports, package.json guidance)",
"type": "module",
"scripts": {
"test": "echo \"The test will be runned when implementation is done.\"",
"test": "node --run test:context-local-variable",
"test:import": "npx codemod jssg test -l typescript ./src/import-process.ts ./tests/import",
"test:export": "npx codemod jssg test -l typescript ./src/export-process.ts ./tests/export",
"test:package-json": "npx codemod jssg test -l json ./src/package-json-process.ts ./tests/package-json",
Expand Down
121 changes: 119 additions & 2 deletions recipes/cjs-to-esm/src/context-local-variable-process.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,131 @@
import type { SgRoot, Edit } from '@codemod.com/jssg-types/main';
import type { SgRoot, Edit, SgNode } from '@codemod.com/jssg-types/main';
import type JS from '@codemod.com/jssg-types/langs/javascript';

/**
* Returns true when `__dirname` or `__filename` resolves to a local definition
* in the current file and should not be transformed.
*
* @example
* // true for: const __dirname = '/tmp';
* isShadowedContextLocal(identifier, root)
*
* @example
* // true for: function fn(__filename) { return __filename; }
* isShadowedContextLocal(identifier, root)
*
* @example
* // false when using Node.js global `__dirname` with no local binding
* isShadowedContextLocal(identifier, root)
*/
const isShadowedContextLocal = (
node: SgNode<JS>,
root: SgRoot<JS>,
): boolean => {
const definition = node.definition();

if (!definition || definition.kind !== 'local') return false;

return definition.root.filename() === root.filename();
};

/**
* @see https://github.com/nodejs/package-examples/blob/main/guide/05-cjs-esm-migration/migrating-context-local-variables/README.md
*/
export default function transform(root: SgRoot<JS>): string | null {
const rootNode = root.root();
const edits: Edit[] = [];

// do some stuff
const requireMainNodes = rootNode.findAll({
rule: {
kind: 'member_expression',
has: {
field: 'object',
kind: 'identifier',
regex: '^require$',
},
all: [
{
has: {
field: 'property',
kind: 'property_identifier',
regex: '^main$',
},
},
],
},
});

for (const node of requireMainNodes) {
const result = node.find({
rule: {
inside: {
kind: 'binary_expression',
has: {
kind: 'identifier',
regex: 'module',
},
},
},
});

if (result) {
edits.push(result.replace('import.meta.main'));
} else {
edits.push(node.replace('import.meta.main'));
}
Comment on lines +71 to +75
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it's a little difficult to quickly see how these lines are different. I think this would both help with that and DRY things up a tiny bit

Suggested change
if (result) {
edits.push(result.replace('import.meta.main'));
} else {
edits.push(node.replace('import.meta.main'));
}
edits.push((result || node).replace('import.meta.main'));

or if result is just potentially nullish

Suggested change
if (result) {
edits.push(result.replace('import.meta.main'));
} else {
edits.push(node.replace('import.meta.main'));
}
edits.push((result ?? node).replace('import.meta.main'));

}

const requireResolveNodes = rootNode.findAll({
rule: {
kind: 'call_expression',
has: {
field: 'function',
kind: 'member_expression',
all: [
{
has: {
field: 'object',
kind: 'identifier',
regex: '^require$',
},
},
{
has: {
field: 'property',
kind: 'property_identifier',
regex: '^resolve$',
},
},
],
},
},
});

for (const node of requireResolveNodes) {
const args = node.field('arguments');
if (args) edits.push(node.replace(`import.meta.resolve${args.text()}`));
}

const contextLocalIdentifiers = rootNode.findAll({
rule: {
kind: 'identifier',
regex: '^(__filename|__dirname)$',
},
});

for (const identifier of contextLocalIdentifiers) {
if (isShadowedContextLocal(identifier, root)) continue;
const name = identifier.text();

switch (name) {
case '__dirname':
edits.push(identifier.replace('import.meta.dirname'));
break;
case '__filename':
edits.push(identifier.replace('import.meta.filename'));
break;
}
}

if (!edits.length) return null;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const __filename = '/tmp/file.js';
const __dirname = '/tmp';

function print(__filename, __dirname) {
return `${__filename}::${__dirname}`;
}

console.log(__filename, __dirname, print('a', 'b'));
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const __filename = '/tmp/file.js';
const __dirname = '/tmp';

function print(__filename, __dirname) {
return `${__filename}::${__dirname}`;
}

console.log(__filename, __dirname, print('a', 'b'));
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const info = {
filename: import.meta.filename,
dirname: import.meta.dirname,
isMain: import.meta.main,
resolved: import.meta.resolve('./foo.js'),
};

console.log(info);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const info = {
filename: import.meta.filename,
dirname: import.meta.dirname,
isMain: import.meta.main,
resolved: import.meta.resolve('./foo.js'),
};

console.log(info);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const info = {
filename: import.meta.filename,
dirname: import.meta.dirname,
isMain: import.meta.main,
resolved: import.meta.resolve('./foo.js'),
};

console.log(info, import.meta.main);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const info = {
filename: __filename,
dirname: __dirname,
isMain: require.main === module,
resolved: require.resolve('./foo.js'),
};

console.log(info, require.main);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
if (import.meta.main) {
boot();
}

const resolved = import.meta.resolve('./foo.js', { paths: [__dirname] });
const filePath = `${import.meta.filename}`;

console.log(import.meta.main, resolved, filePath);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
if (require.main === module) {
boot();
}

const resolved = require.resolve('./foo.js', { paths: [__dirname] });
const filePath = `${__filename}`;

console.log(require.main, resolved, filePath);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const currentModule = module;
console.log(exports.value, currentModule.id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this one. This looks like CJS, but it isn't transformed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh I didn't recall why I was requiring this test
maybe we can remove it

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const currentModule = module;
console.log(exports.value, currentModule.id);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const shouldRun = Boolean(import.meta.main);

if (shouldRun) {
run();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const shouldRun = Boolean(require.main);

if (shouldRun) {
run();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if (import.meta.main) {
start();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if (module === require.main) {
start();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
console.log(import.meta.dirname);

function read(__filename) {
return __filename;
}

{
const __dirname = 'tmp';
console.log(__dirname);
}

console.log(import.meta.filename);
12 changes: 12 additions & 0 deletions recipes/cjs-to-esm/tests/context-local-variable/shadowed/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
console.log(__dirname);

function read(__filename) {
return __filename;
}

{
const __dirname = 'tmp';
console.log(__dirname);
}

console.log(__filename);