pruny 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +137 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9394,17 +9394,54 @@ async function scanUnusedExports(config) {
9394
9394
  for (const [file, exports] of exportMap.entries()) {
9395
9395
  for (const exp of exports) {
9396
9396
  let isUsed = false;
9397
+ let usedInternally = false;
9398
+ const fileContent = totalContents.get(file);
9399
+ if (fileContent) {
9400
+ const lines = fileContent.split(`
9401
+ `);
9402
+ for (let i = 0;i < lines.length; i++) {
9403
+ if (i === exp.line - 1)
9404
+ continue;
9405
+ const line = lines[i];
9406
+ if (isCommentOrString(line))
9407
+ continue;
9408
+ const cleanLine = stripStringsAndComments(line);
9409
+ const referenceRegex = new RegExp(`\\b${exp.name}\\b`);
9410
+ if (referenceRegex.test(cleanLine)) {
9411
+ usedInternally = true;
9412
+ break;
9413
+ }
9414
+ }
9415
+ }
9397
9416
  for (const [otherFile, content] of totalContents.entries()) {
9398
9417
  if (file === otherFile)
9399
9418
  continue;
9400
- const referenceRegex = new RegExp(`\\b${exp.name}\\b`);
9401
- if (referenceRegex.test(content)) {
9419
+ const jsxPattern = new RegExp(`<${exp.name}[\\s/>]`);
9420
+ const importPattern = new RegExp(`import.*\\b${exp.name}\\b.*from`);
9421
+ const destructurePattern = new RegExp(`\\{[^}]*\\b${exp.name}\\b[^}]*\\}`);
9422
+ if (jsxPattern.test(content) || importPattern.test(content)) {
9402
9423
  isUsed = true;
9403
9424
  break;
9404
9425
  }
9426
+ if (!isUsed) {
9427
+ const lines = content.split(`
9428
+ `);
9429
+ for (const line of lines) {
9430
+ if (isCommentOrString(line))
9431
+ continue;
9432
+ const cleanLine = stripStringsAndComments(line);
9433
+ const codeUsagePattern = new RegExp(`\\b${exp.name}\\s*[({]|\\b${exp.name}\\s*\\.|\\b${exp.name}\\s*,|\\b${exp.name}\\s*;|\\b${exp.name}\\s*\\)`);
9434
+ if (codeUsagePattern.test(cleanLine)) {
9435
+ isUsed = true;
9436
+ break;
9437
+ }
9438
+ }
9439
+ }
9440
+ if (isUsed)
9441
+ break;
9405
9442
  }
9406
9443
  if (!isUsed) {
9407
- unusedExports.push(exp);
9444
+ unusedExports.push({ ...exp, usedInternally });
9408
9445
  }
9409
9446
  }
9410
9447
  }
@@ -9415,6 +9452,24 @@ async function scanUnusedExports(config) {
9415
9452
  exports: unusedExports
9416
9453
  };
9417
9454
  }
9455
+ function isCommentOrString(line) {
9456
+ const trimmed = line.trim();
9457
+ if (trimmed.startsWith("//"))
9458
+ return true;
9459
+ if (trimmed.startsWith("/*") || trimmed.startsWith("*"))
9460
+ return true;
9461
+ if (trimmed.includes("{/*") || trimmed.includes("*/}"))
9462
+ return true;
9463
+ return false;
9464
+ }
9465
+ function stripStringsAndComments(line) {
9466
+ let result = line;
9467
+ result = result.replace(/\/\/.*$/g, "");
9468
+ result = result.replace(/'([^'\\]|\\.)*'/g, "''");
9469
+ result = result.replace(/"([^"\\]|\\.)*"/g, '""');
9470
+ result = result.replace(/`([^`\\]|\\.)*`/g, "``");
9471
+ return result;
9472
+ }
9418
9473
 
9419
9474
  // src/scanner.ts
9420
9475
  function extractRoutePath(filePath) {
@@ -9742,7 +9797,7 @@ function findConfigFile(dir) {
9742
9797
  }
9743
9798
 
9744
9799
  // src/fixer.ts
9745
- import { readFileSync as readFileSync6, writeFileSync } from "node:fs";
9800
+ import { readFileSync as readFileSync6, writeFileSync, unlinkSync } from "node:fs";
9746
9801
  import { join as join6 } from "node:path";
9747
9802
  function removeExportFromLine(rootDir, exp) {
9748
9803
  const fullPath = join6(rootDir, exp.file);
@@ -9751,6 +9806,20 @@ function removeExportFromLine(rootDir, exp) {
9751
9806
  const lines = content.split(`
9752
9807
  `);
9753
9808
  const lineIndex = exp.line - 1;
9809
+ if (!exp.usedInternally) {
9810
+ const deletedLines = deleteDeclaration(lines, lineIndex);
9811
+ if (deletedLines > 0) {
9812
+ const newContent = lines.join(`
9813
+ `);
9814
+ if (isFileEmpty(newContent)) {
9815
+ unlinkSync(fullPath);
9816
+ return true;
9817
+ }
9818
+ writeFileSync(fullPath, newContent, "utf-8");
9819
+ return true;
9820
+ }
9821
+ return false;
9822
+ }
9754
9823
  const originalLine = lines[lineIndex];
9755
9824
  const exportPrefixRegex = /^(export\s+(?:async\s+)?)/;
9756
9825
  if (exportPrefixRegex.test(originalLine.trim())) {
@@ -9766,13 +9835,61 @@ function removeExportFromLine(rootDir, exp) {
9766
9835
  return false;
9767
9836
  }
9768
9837
  }
9838
+ function deleteDeclaration(lines, startLine) {
9839
+ if (startLine >= lines.length)
9840
+ return 0;
9841
+ let endLine = startLine;
9842
+ let braceCount = 0;
9843
+ let foundClosing = false;
9844
+ for (let i = startLine;i < lines.length; i++) {
9845
+ const line = lines[i];
9846
+ const openBraces = (line.match(/{/g) || []).length;
9847
+ const closeBraces = (line.match(/}/g) || []).length;
9848
+ braceCount += openBraces - closeBraces;
9849
+ if (braceCount === 0) {
9850
+ if (line.includes(";") || line.includes("};")) {
9851
+ endLine = i;
9852
+ foundClosing = true;
9853
+ break;
9854
+ }
9855
+ if (i > startLine && line.trim() === "}") {
9856
+ endLine = i;
9857
+ foundClosing = true;
9858
+ break;
9859
+ }
9860
+ }
9861
+ }
9862
+ if (!foundClosing && braceCount === 0) {
9863
+ endLine = startLine;
9864
+ }
9865
+ const linesToDelete = endLine - startLine + 1;
9866
+ lines.splice(startLine, linesToDelete);
9867
+ return linesToDelete;
9868
+ }
9869
+ function isFileEmpty(content) {
9870
+ const lines = content.split(`
9871
+ `);
9872
+ for (const line of lines) {
9873
+ const trimmed = line.trim();
9874
+ if (!trimmed)
9875
+ continue;
9876
+ if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*"))
9877
+ continue;
9878
+ if (trimmed.startsWith("import ") || trimmed.startsWith("export "))
9879
+ continue;
9880
+ if (trimmed === '"use client";' || trimmed === '"use server";' || trimmed === "'use client';" || trimmed === "'use server';")
9881
+ continue;
9882
+ return false;
9883
+ }
9884
+ return true;
9885
+ }
9769
9886
 
9770
9887
  // src/init.ts
9771
- import { writeFileSync as writeFileSync2, existsSync as existsSync5 } from "node:fs";
9888
+ import { writeFileSync as writeFileSync2, existsSync as existsSync6 } from "node:fs";
9772
9889
  import { join as join7 } from "node:path";
9773
9890
  function init(cwd = process.cwd()) {
9774
9891
  const configPath = join7(cwd, "pruny.config.json");
9775
- if (existsSync5(configPath)) {
9892
+ if (existsSync6(configPath)) {
9776
9893
  console.log(source_default.yellow("⚠️ pruny.config.json already exists. Skipping."));
9777
9894
  return;
9778
9895
  }
@@ -10005,11 +10122,21 @@ program2.action(async (options) => {
10005
10122
  if (result.unusedExports && result.unusedExports.exports.length > 0) {
10006
10123
  console.log(source_default.yellow.bold(`\uD83D\uDD27 Fixing unused exports (removing "export" keyword)...
10007
10124
  `));
10008
- let fixedCount = 0;
10125
+ const exportsByFile = new Map;
10009
10126
  for (const exp of result.unusedExports.exports) {
10010
- if (removeExportFromLine(config.dir, exp)) {
10011
- console.log(source_default.green(` Fixed: ${exp.name} in ${exp.file}`));
10012
- fixedCount++;
10127
+ if (!exportsByFile.has(exp.file)) {
10128
+ exportsByFile.set(exp.file, []);
10129
+ }
10130
+ exportsByFile.get(exp.file).push(exp);
10131
+ }
10132
+ let fixedCount = 0;
10133
+ for (const [file, exports] of exportsByFile.entries()) {
10134
+ const sortedExports = exports.sort((a, b) => b.line - a.line);
10135
+ for (const exp of sortedExports) {
10136
+ if (removeExportFromLine(config.dir, exp)) {
10137
+ console.log(source_default.green(` Fixed: ${exp.name} in ${exp.file}`));
10138
+ fixedCount++;
10139
+ }
10013
10140
  }
10014
10141
  }
10015
10142
  if (fixedCount > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pruny",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Find and remove unused Next.js API routes & Nest.js Controllers",
5
5
  "type": "module",
6
6
  "files": [