All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.cjs.configs.scripts.dead-code-eliminator.index.js Maven / Gradle / Ivy

Go to download

A package encapsulating common code across neeto projects including initializers, utility functions, common components and hooks and so on.

There is a newer version: 4.12.3
Show newest version
#!/usr/bin/env node
/* eslint-disable no-console */

const { exec, execSync } = require("child_process");
const fs = require("fs");
const pathLib = require("path");

const generate = require("@babel/generator").default;
const babelParser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { isNotEmpty, existsBy } = require("@bigbinary/neeto-cist");
const enhancedResolve = require("enhanced-resolve");
const { isEmpty, includes, __ } = require("ramda");

const {
  RAILS_MOUNTED_COMPONENTS_REGEX,
  TYPES,
  PACKS_FILES_REGEX,
} = require("./constants");

// @ts-ignore
// eslint-disable-next-line import/extensions
const webpackConfig = require("../../../../../../config/webpack/webpack.config.js");

let modifiedFiles = [];
let importedItems = [];
let unusedFiles = [];
let iterationCount = 1;

const javascriptDir = pathLib.resolve(
  __dirname,
  "../../../../../../app/javascript"
);

const resolver = enhancedResolve.create.sync({
  alias: webpackConfig.resolve.alias,
  modules: webpackConfig.resolve.modules,
  extensions: webpackConfig.resolve.extensions,
  fallback: webpackConfig.resolve.fallback,
  conditionNames: ["require", "import", "node", "default", "types"],
});

const isRailsMountedOrPacksFiles = filePath =>
  RAILS_MOUNTED_COMPONENTS_REGEX.test(filePath) ||
  PACKS_FILES_REGEX.test(filePath);

const isInImportedList = (item, filePath) =>
  existsBy({ item: includes(__, [item, "*"]), filePath }, importedItems);

const findImports = filePath => {
  const code = fs.readFileSync(filePath, "utf8");
  const ast = babelParser.parse(code, {
    sourceType: "module",
    plugins: ["jsx"],
  });

  traverse(ast, {
    ImportDeclaration(path) {
      const { node } = path;

      const currentDir = pathLib.dirname(filePath);
      const importSource = node.source.value;

      const resolvedPath = resolver(currentDir, importSource);
      const isNodeModule = resolvedPath.includes("node_modules");
      if (isNodeModule) return;

      if (isEmpty(node.specifiers)) {
        importedItems.push({ item: "*", filePath: resolvedPath });
      }

      node.specifiers.forEach(specifier => {
        if (specifier.type === TYPES.IMPORT_NAMESPACE_SPECIFIER) {
          importedItems.push({ item: "*", filePath: resolvedPath });
        } else if (specifier.type === TYPES.IMPORT_DEFAULT_SPECIFIER) {
          importedItems.push({ item: "default", filePath: resolvedPath });
        } else {
          const item = specifier.imported.name;
          importedItems.push({ item, filePath: resolvedPath });
        }
      });
    },
  });
};

const removeUnusedExports = filePath => {
  if (isRailsMountedOrPacksFiles(filePath)) return;

  if (!existsBy({ filePath }, importedItems)) {
    unusedFiles.push(filePath);

    return;
  }

  let isPathModified = false;
  const code = fs.readFileSync(filePath, "utf8");
  const ast = babelParser.parse(code, {
    sourceType: "module",
    plugins: ["jsx"],
  });

  traverse(ast, {
    ExportNamedDeclaration(path) {
      const { node } = path;
      if (node.declaration) {
        if (node.declaration.type === TYPES.VARIABLE_DECLARATION) {
          node.declaration.declarations.forEach(declaration => {
            const exportedItem = declaration.id.name;
            if (isInImportedList(exportedItem, filePath)) return;

            path.replaceWith(path.node.declaration);
            isPathModified = true;
          });
        } else if (
          node.declaration.type === TYPES.FUNCTION_DECLARATION ||
          node.declaration.type === TYPES.CLASS_DECLARATION
        ) {
          const exportedItem = node.declaration.id.name;
          if (isInImportedList(exportedItem, filePath)) return;

          path.replaceWith(path.node.declaration);
          isPathModified = true;
        }
      } else {
        path.get("specifiers").forEach(specifierPath => {
          const exportedItem = specifierPath.node.exported.name;
          if (isInImportedList(exportedItem, filePath)) return;

          node.specifiers.length === 1 ? path.remove() : specifierPath.remove();
          isPathModified = true;
        });
      }
    },
    ExportDefaultDeclaration(path) {
      const exportedItem = "default";
      if (isInImportedList(exportedItem, filePath)) return;

      path.remove();
      isPathModified = true;
    },
  });

  if (!isPathModified) return;

  const newCode = generate(ast, {
    retainLines: true,
    retainFunctionParens: true,
  }).code;

  fs.writeFileSync(filePath, newCode);
  modifiedFiles.push(filePath);
};

const traverseDirectoryAndExecuteFunc = (dir, func) => {
  const files = fs.readdirSync(dir);

  files.forEach(file => {
    const filePath = pathLib.join(dir, file);
    const stats = fs.statSync(filePath);

    if (stats.isFile() && [".js", ".jsx"].includes(pathLib.extname(filePath))) {
      func(filePath);
    } else if (stats.isDirectory()) {
      traverseDirectoryAndExecuteFunc(filePath, func);
    }
  });
};

const runLintersAndRemoveUnusedFiles = () => {
  const commands = [];

  if (isNotEmpty(unusedFiles)) commands.push(`rm -f ${unusedFiles.join(" ")}`);

  if (isNotEmpty(modifiedFiles)) {
    commands.push(`npx prettier --write ${modifiedFiles.join(" ")}`);
    commands.push(`npx eslint --fix ${modifiedFiles.join(" ")}`);
  }

  // script will be executed recursively until we have no modified files or unused files
  exec(commands.join(" && "), () => run());
};

const restoreEslintNeetoVersion = () => {
  execSync("git restore package.json yarn.lock", { stdio: "ignore" });
  execSync("yarn", { stdio: "ignore" });
};

const reportNonFixableEslintErrors = () => {
  const modifiedFiles = execSync("git diff --name-only --diff-filter=M")
    .toString()
    .split("\n")
    .join(" ");

  if (!modifiedFiles) {
    console.log("✅  Script executed successfully!\n");

    return;
  }

  exec(`npx eslint ${modifiedFiles} --fix --quiet`, (_, stdout) => {
    if (stdout) {
      console.log(stdout);
      console.log(
        "⭕️ Auto-fixing is not implemented for the above eslint errors. Please fix them manually.\n"
      );
    } else {
      console.log("- No errors found.\n\n✅  Script executed successfully!\n");
    }
  });
};

const postExecution = () => {
  console.log("\n4. Restoring @bigbinary/eslint-plugin-neeto version.");
  restoreEslintNeetoVersion();

  console.log("\n5. Checking if there are any non-fixable eslint errors.");
  reportNonFixableEslintErrors();
};

const installCustomEslintNeetoVersion = () =>
  execSync(
    "yarn add @bigbinary/[email protected]",
    { stdio: "ignore" }
  );

const run = () => {
  console.log(`\nIteration: ${iterationCount++}`);
  console.log(".............");

  modifiedFiles = [];
  importedItems = [];
  unusedFiles = [];

  console.log("* Collecting all the imports.");
  traverseDirectoryAndExecuteFunc(javascriptDir, findImports);

  console.log("* Removing exports which are not imported.");
  traverseDirectoryAndExecuteFunc(javascriptDir, removeUnusedExports);

  const filesCountMsg = `\nModified files: ${modifiedFiles.length}\nUnused files: ${unusedFiles.length}`;

  const isExecutionCompleted = isEmpty(modifiedFiles) && isEmpty(unusedFiles);
  if (isExecutionCompleted) {
    console.log(filesCountMsg);
    postExecution();
  } else {
    console.log(
      "* Running linters on modified files and removing unused files."
    );
    runLintersAndRemoveUnusedFiles();
    console.log(filesCountMsg);
  }
};

// Script execution starts here
console.log("\nPlease wait, this may take a few minutes...");

console.log(
  "\n1. Installing @bigbinary/eslint-plugin-neeto custom release required for this script."
);
installCustomEslintNeetoVersion();

console.log(
  "\n2. Executing script recursively until we have no modified or unused files."
);
run();




© 2015 - 2024 Weber Informatics LLC | Privacy Policy