Replacing `console.log` with custom `logger`

readme.mdMarkdownreplace-console-with-logger.jsJavaScript

readme.md

Setup Instructions

1. Install Dependencies

Install jscodeshift globally if not already installed:

npm install -g jscodeshift

Install dev dependencies

npm i -D @babel/parser @babel/traverse @babel/types @babel/generator

2. Run the Codemod

Run the codemod across your project with the following command:

jscodeshift -t replace-console-with-logger.js --parser=babel --extensions=ts,tsx,js,jsx src/

Explanation of Options:

  • -t replace-console-with-logger.js: Specifies the codemod script.
  • --extensions=ts,tsx,js,jsx: Tells jscodeshift to process files with these extensions.
  • --parser=babel-ts: Uses Babel to parse TypeScript and JavaScript.
  • src/: The root directory of your source files.

Testing the Codemod

  1. Choose a small subset of your files and run the codemod on them:
    jscodeshift -t replace-console-with-logger.js --extensions=ts,tsx,js,jsx --parser=babel-ts src/some/subdirectory
    
  2. Verify that:
    • All console.log, console.warn, and console.error are replaced with logger.log, logger.warn, and logger.error.
    • import { logger } from "~/logger"; is correctly added at the top of the file (if not already present).

Handling Edge Cases

If you encounter any parsing issues, ensure that:

  1. The project uses valid TypeScript/JavaScript syntax.
  2. JSX components are properly structured.

replace-console-with-logger.js

const { parse } = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generate = require("@babel/generator").default;

module.exports = function transformer(fileInfo, api) {
  const j = api.jscodeshift;
  const source = fileInfo.source;

  const ast = parse(source, {
    sourceType: "module",
    plugins: ["typescript", "jsx"], // Support TS, TSX, JS, and JSX syntax
  });

  let loggerImported = false;

  // Check if `logger` is already imported
  traverse(ast, {
    ImportDeclaration(path) {
      if (path.node.source.value === "~/logger") {
        loggerImported = true;
      }
    },
  });

  if (!loggerImported) {
    // Add `import { logger } from "~/logger";` at the top
    const importDeclaration = t.importDeclaration(
      [t.importSpecifier(t.identifier("logger"), t.identifier("logger"))],
      t.stringLiteral("~/logger")
    );
    ast.program.body.unshift(importDeclaration);
  }

  // Replace console.* calls with logger.*
  traverse(ast, {
    CallExpression(path) {
      const callee = path.node.callee;

      if (
        t.isMemberExpression(callee) &&
        t.isIdentifier(callee.object, { name: "console" }) &&
        ["log", "warn", "error"].includes(callee.property.name)
      ) {
        const loggerMethod = t.memberExpression(
          t.identifier("logger"),
          t.identifier(callee.property.name)
        );
        path.node.callee = loggerMethod;
      }
    },
  });

  return generate(ast).code;
};
Updated: 12/3/2024