Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More β†’
Socket
Sign inDemoInstall
Socket

@line/ts-remove-unused

Package Overview
Dependencies
Maintainers
0
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@line/ts-remove-unused - npm Package Compare versions

Comparing version 0.9.0 to 0.10.0

276

dist/cli.js

@@ -411,12 +411,4 @@ #!/usr/bin/env node

// lib/util/memoize.ts
import { parentPort } from "node:worker_threads";
var memoize = (fn2, { key, name }) => {
var memoize = (fn2, { key }) => {
const cache = /* @__PURE__ */ new Map();
if (parentPort) {
parentPort.on("message", (message) => {
if ("broadcast" in message && message.broadcast.name === name) {
cache.set(message.broadcast.key, message.broadcast.value);
}
});
}
return (...args) => {

@@ -429,11 +421,2 @@ const k = key(...args);

cache.set(k, result);
if (parentPort) {
parentPort.postMessage({
broadcast: {
name,
key: k,
value: result
}
});
}
return result;

@@ -550,3 +533,3 @@ };

}
if (ts3.isFunctionDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isClassDeclaration(node)) {
if (ts3.isFunctionDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isClassDeclaration(node) || ts3.isEnumDeclaration(node)) {
const isExported = node.modifiers?.some(

@@ -748,4 +731,3 @@ (m) => m.kind === ts3.SyntaxKind.ExportKeyword

options: arg.options
}),
name: "parseFile"
})
});

@@ -901,33 +883,56 @@

// lib/util/edit.ts
var stripExportKeyword = (syntaxList) => {
const file = ts6.createSourceFile(
"tmp.ts",
var transform = (source, transformer) => {
const file = ts6.createSourceFile("file.ts", source, ts6.ScriptTarget.Latest);
const result = ts6.transform(file, [transformer]).transformed[0];
const printer = ts6.createPrinter();
return result ? printer.printFile(result).trim() : "";
};
var stripFunctionExportKeyword = (syntaxList) => {
const code = transform(
`${syntaxList} function f() {}`,
ts6.ScriptTarget.Latest
(context) => (rootNode) => {
const visitor = (node) => {
if (ts6.isFunctionDeclaration(node)) {
return ts6.factory.createFunctionDeclaration(
node.modifiers?.filter(
(v) => v.kind !== ts6.SyntaxKind.ExportKeyword && v.kind !== ts6.SyntaxKind.DefaultKeyword
),
node.asteriskToken,
node.name,
node.typeParameters,
node.parameters,
node.type,
node.body
);
}
return ts6.visitEachChild(node, visitor, context);
};
return ts6.visitEachChild(rootNode, visitor, context);
}
);
const transformer = (context) => (rootNode) => {
const visitor = (node) => {
if (ts6.isFunctionDeclaration(node)) {
return ts6.factory.createFunctionDeclaration(
node.modifiers?.filter(
(v) => v.kind !== ts6.SyntaxKind.ExportKeyword && v.kind !== ts6.SyntaxKind.DefaultKeyword
),
node.asteriskToken,
node.name,
node.typeParameters,
node.parameters,
node.type,
node.body
);
}
return ts6.visitEachChild(node, visitor, context);
};
return ts6.visitEachChild(rootNode, visitor, context);
};
const result = ts6.transform(file, [transformer]).transformed[0];
const printer = ts6.createPrinter();
const code = result ? printer.printFile(result).trim() : "";
const pos = code.indexOf("function");
return code.slice(0, pos);
};
var stripEnumExportKeyword = (syntaxList) => {
const code = transform(
`${syntaxList} enum E {}`,
(context) => (rootNode) => {
const visitor = (node) => {
if (ts6.isEnumDeclaration(node)) {
return ts6.factory.createEnumDeclaration(
node.modifiers?.filter(
(v) => v.kind !== ts6.SyntaxKind.ExportKeyword
),
node.name,
node.members
);
}
return ts6.visitEachChild(node, visitor, context);
};
return ts6.visitEachChild(rootNode, visitor, context);
}
);
const pos = code.indexOf("enum");
return code.slice(0, pos);
};
var disabledEditTracker = {

@@ -1078,3 +1083,3 @@ start: () => {

changes.push({
newText: item.change.isUnnamedDefaultExport ? "" : stripExportKeyword(item.change.code),
newText: item.change.isUnnamedDefaultExport ? "" : stripFunctionExportKeyword(item.change.code),
span: item.change.span

@@ -1119,2 +1124,17 @@ });

}
case ts6.SyntaxKind.EnumDeclaration: {
if (item.skip || usage.has(item.name)) {
break;
}
changes.push({
newText: stripEnumExportKeyword(item.change.code),
span: item.change.span
});
logs.push({
fileName: targetFile,
position: item.start,
code: item.name
});
break;
}
case ts6.SyntaxKind.ExportAssignment: {

@@ -1325,3 +1345,2 @@ if (item.skip || usage.has("default")) {

projectRoot = ".",
pool,
recursive

@@ -1351,4 +1370,3 @@ }) => {

}
const fn2 = pool ? pool.run.bind(pool) : processFile;
const result = await fn2({
const result = processFile({
targetFile: c.file,

@@ -1582,146 +1600,2 @@ vertexes: dependencyGraph.eject(),

// lib/util/WorkerPool.ts
import { Worker } from "node:worker_threads";
import * as os from "node:os";
var generateCode = (url, name) => `data:text/javascript,import { parentPort } from 'node:worker_threads';
import { ${name} } from '${url}';
parentPort.on('message', async (message) => {
if (!('arg' in message)) {
return;
}
try {
const result = await ${name}(message.arg);
parentPort.postMessage({ result });
} catch (error) {
parentPort.postMessage({ error });
}
});
`;
var PromiseWorker = class extends Worker {
current = null;
constructor(url, name) {
super(new URL(generateCode(url, name)));
}
};
var WorkerPool = class {
#url;
#name;
#queue = [];
#working = [];
#idle = [];
constructor({ url, name }) {
this.#url = url;
this.#name = name;
const max = os.availableParallelism?.() ?? os.cpus().length - 1;
for (let i = 0; i < max; i++) {
this.#setup();
}
}
run(arg) {
return new Promise((resolve3, reject) => {
const worker = this.#idle.pop();
if (!worker) {
this.#queue.push({ resolve: resolve3, reject, arg });
return;
}
worker.current = {
resolve: resolve3,
reject
};
this.#working.push(worker);
worker.postMessage({ arg });
});
}
close() {
if (this.#working.length > 0) {
throw new Error("Cannot close while there are working workers");
}
return Promise.all(
this.#idle.map((worker) => {
worker.removeAllListeners();
return worker.terminate();
})
);
}
#assignTask() {
if (this.#queue.length === 0) {
return;
}
const worker = this.#idle.pop();
if (!worker) {
return;
}
const { resolve: resolve3, reject, arg } = this.#queue.shift();
worker.current = {
resolve: resolve3,
reject
};
worker.postMessage({ arg });
this.#working.push(worker);
}
#free(worker) {
worker.current = null;
const index = this.#working.indexOf(worker);
if (index === -1) {
return;
}
this.#working.splice(index, 1);
this.#idle.push(worker);
}
#setup() {
const worker = new PromiseWorker(this.#url, this.#name);
worker.on("message", (message) => {
if (!worker.current) {
return;
}
switch (true) {
case "broadcast" in message: {
[...this.#working, ...this.#idle].forEach(
(w) => w !== worker && w.postMessage(message)
);
return;
}
case "error" in message: {
worker.current.reject(message.error);
this.#free(worker);
this.#assignTask();
return;
}
case "result" in message: {
worker.current.resolve(message.result);
this.#free(worker);
this.#assignTask();
return;
}
}
});
worker.on("error", (error) => {
if (!worker.current) {
return;
}
worker.current.reject(error);
this.#free(worker);
this.#assignTask();
});
worker.on("exit", (code) => {
if (code === 0) {
return;
}
if (worker.current) {
worker.current.reject(
new Error(`Worker stopped with exit code ${code}`)
);
this.#free(worker);
worker.terminate();
this.#setup();
return;
}
worker.terminate();
this.#setup();
});
this.#idle.push(worker);
}
};
// lib/util/regex.ts

@@ -1792,6 +1666,2 @@ var dts = /\.d\.ts$/;

);
const pool = new WorkerPool({
name: "processFile",
url: globalThis.__INTERNAL_WORKER_URL__ || new URL("./worker.js", import.meta.url).href
});
await edit({

@@ -1801,7 +1671,6 @@ fileService,

deleteUnusedFile: true,
enableCodeFix: true,
enableCodeFix: mode === "write" || recursive,
editTracker,
options,
projectRoot,
pool,
recursive

@@ -1826,3 +1695,2 @@ });

editTracker.logResult();
await pool.close();
if (mode === "check" && !editTracker.isClean) {

@@ -1838,3 +1706,3 @@ system.exit(1);

var cli = cac("ts-remove-unused");
cli.command("", "There are no subcommands. Simply execute ts-remove-unused").option("--project <file>", "Path to your tsconfig.json").option(
cli.command("", "There are no subcommands. Simply execute ts-remove-unused").option("-p, --project <file>", "Path to your tsconfig.json").option(
"--skip <regexp_pattern>",

@@ -1859,3 +1727,3 @@ "Specify the regexp pattern to match files that should be skipped from transforming"

projectRoot: cwd2(),
recursive: !!options.experimentalRecursive
recursive: !!options.recursive
});

@@ -1862,0 +1730,0 @@ });

@@ -406,12 +406,4 @@ // lib/remove.ts

// lib/util/memoize.ts
import { parentPort } from "node:worker_threads";
var memoize = (fn2, { key, name }) => {
var memoize = (fn2, { key }) => {
const cache = /* @__PURE__ */ new Map();
if (parentPort) {
parentPort.on("message", (message) => {
if ("broadcast" in message && message.broadcast.name === name) {
cache.set(message.broadcast.key, message.broadcast.value);
}
});
}
return (...args) => {

@@ -424,11 +416,2 @@ const k = key(...args);

cache.set(k, result);
if (parentPort) {
parentPort.postMessage({
broadcast: {
name,
key: k,
value: result
}
});
}
return result;

@@ -545,3 +528,3 @@ };

}
if (ts3.isFunctionDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isClassDeclaration(node)) {
if (ts3.isFunctionDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isClassDeclaration(node) || ts3.isEnumDeclaration(node)) {
const isExported = node.modifiers?.some(

@@ -743,4 +726,3 @@ (m) => m.kind === ts3.SyntaxKind.ExportKeyword

options: arg.options
}),
name: "parseFile"
})
});

@@ -896,33 +878,56 @@

// lib/util/edit.ts
var stripExportKeyword = (syntaxList) => {
const file = ts6.createSourceFile(
"tmp.ts",
var transform = (source, transformer) => {
const file = ts6.createSourceFile("file.ts", source, ts6.ScriptTarget.Latest);
const result = ts6.transform(file, [transformer]).transformed[0];
const printer = ts6.createPrinter();
return result ? printer.printFile(result).trim() : "";
};
var stripFunctionExportKeyword = (syntaxList) => {
const code = transform(
`${syntaxList} function f() {}`,
ts6.ScriptTarget.Latest
(context) => (rootNode) => {
const visitor = (node) => {
if (ts6.isFunctionDeclaration(node)) {
return ts6.factory.createFunctionDeclaration(
node.modifiers?.filter(
(v) => v.kind !== ts6.SyntaxKind.ExportKeyword && v.kind !== ts6.SyntaxKind.DefaultKeyword
),
node.asteriskToken,
node.name,
node.typeParameters,
node.parameters,
node.type,
node.body
);
}
return ts6.visitEachChild(node, visitor, context);
};
return ts6.visitEachChild(rootNode, visitor, context);
}
);
const transformer = (context) => (rootNode) => {
const visitor = (node) => {
if (ts6.isFunctionDeclaration(node)) {
return ts6.factory.createFunctionDeclaration(
node.modifiers?.filter(
(v) => v.kind !== ts6.SyntaxKind.ExportKeyword && v.kind !== ts6.SyntaxKind.DefaultKeyword
),
node.asteriskToken,
node.name,
node.typeParameters,
node.parameters,
node.type,
node.body
);
}
return ts6.visitEachChild(node, visitor, context);
};
return ts6.visitEachChild(rootNode, visitor, context);
};
const result = ts6.transform(file, [transformer]).transformed[0];
const printer = ts6.createPrinter();
const code = result ? printer.printFile(result).trim() : "";
const pos = code.indexOf("function");
return code.slice(0, pos);
};
var stripEnumExportKeyword = (syntaxList) => {
const code = transform(
`${syntaxList} enum E {}`,
(context) => (rootNode) => {
const visitor = (node) => {
if (ts6.isEnumDeclaration(node)) {
return ts6.factory.createEnumDeclaration(
node.modifiers?.filter(
(v) => v.kind !== ts6.SyntaxKind.ExportKeyword
),
node.name,
node.members
);
}
return ts6.visitEachChild(node, visitor, context);
};
return ts6.visitEachChild(rootNode, visitor, context);
}
);
const pos = code.indexOf("enum");
return code.slice(0, pos);
};
var disabledEditTracker = {

@@ -1073,3 +1078,3 @@ start: () => {

changes.push({
newText: item.change.isUnnamedDefaultExport ? "" : stripExportKeyword(item.change.code),
newText: item.change.isUnnamedDefaultExport ? "" : stripFunctionExportKeyword(item.change.code),
span: item.change.span

@@ -1114,2 +1119,17 @@ });

}
case ts6.SyntaxKind.EnumDeclaration: {
if (item.skip || usage.has(item.name)) {
break;
}
changes.push({
newText: stripEnumExportKeyword(item.change.code),
span: item.change.span
});
logs.push({
fileName: targetFile,
position: item.start,
code: item.name
});
break;
}
case ts6.SyntaxKind.ExportAssignment: {

@@ -1320,3 +1340,2 @@ if (item.skip || usage.has("default")) {

projectRoot = ".",
pool,
recursive

@@ -1346,4 +1365,3 @@ }) => {

}
const fn2 = pool ? pool.run.bind(pool) : processFile;
const result = await fn2({
const result = processFile({
targetFile: c.file,

@@ -1577,146 +1595,2 @@ vertexes: dependencyGraph.eject(),

// lib/util/WorkerPool.ts
import { Worker } from "node:worker_threads";
import * as os from "node:os";
var generateCode = (url, name) => `data:text/javascript,import { parentPort } from 'node:worker_threads';
import { ${name} } from '${url}';
parentPort.on('message', async (message) => {
if (!('arg' in message)) {
return;
}
try {
const result = await ${name}(message.arg);
parentPort.postMessage({ result });
} catch (error) {
parentPort.postMessage({ error });
}
});
`;
var PromiseWorker = class extends Worker {
current = null;
constructor(url, name) {
super(new URL(generateCode(url, name)));
}
};
var WorkerPool = class {
#url;
#name;
#queue = [];
#working = [];
#idle = [];
constructor({ url, name }) {
this.#url = url;
this.#name = name;
const max = os.availableParallelism?.() ?? os.cpus().length - 1;
for (let i = 0; i < max; i++) {
this.#setup();
}
}
run(arg) {
return new Promise((resolve2, reject) => {
const worker = this.#idle.pop();
if (!worker) {
this.#queue.push({ resolve: resolve2, reject, arg });
return;
}
worker.current = {
resolve: resolve2,
reject
};
this.#working.push(worker);
worker.postMessage({ arg });
});
}
close() {
if (this.#working.length > 0) {
throw new Error("Cannot close while there are working workers");
}
return Promise.all(
this.#idle.map((worker) => {
worker.removeAllListeners();
return worker.terminate();
})
);
}
#assignTask() {
if (this.#queue.length === 0) {
return;
}
const worker = this.#idle.pop();
if (!worker) {
return;
}
const { resolve: resolve2, reject, arg } = this.#queue.shift();
worker.current = {
resolve: resolve2,
reject
};
worker.postMessage({ arg });
this.#working.push(worker);
}
#free(worker) {
worker.current = null;
const index = this.#working.indexOf(worker);
if (index === -1) {
return;
}
this.#working.splice(index, 1);
this.#idle.push(worker);
}
#setup() {
const worker = new PromiseWorker(this.#url, this.#name);
worker.on("message", (message) => {
if (!worker.current) {
return;
}
switch (true) {
case "broadcast" in message: {
[...this.#working, ...this.#idle].forEach(
(w) => w !== worker && w.postMessage(message)
);
return;
}
case "error" in message: {
worker.current.reject(message.error);
this.#free(worker);
this.#assignTask();
return;
}
case "result" in message: {
worker.current.resolve(message.result);
this.#free(worker);
this.#assignTask();
return;
}
}
});
worker.on("error", (error) => {
if (!worker.current) {
return;
}
worker.current.reject(error);
this.#free(worker);
this.#assignTask();
});
worker.on("exit", (code) => {
if (code === 0) {
return;
}
if (worker.current) {
worker.current.reject(
new Error(`Worker stopped with exit code ${code}`)
);
this.#free(worker);
worker.terminate();
this.#setup();
return;
}
worker.terminate();
this.#setup();
});
this.#idle.push(worker);
}
};
// lib/util/regex.ts

@@ -1787,6 +1661,2 @@ var dts = /\.d\.ts$/;

);
const pool = new WorkerPool({
name: "processFile",
url: globalThis.__INTERNAL_WORKER_URL__ || new URL("./worker.js", import.meta.url).href
});
await edit({

@@ -1796,7 +1666,6 @@ fileService,

deleteUnusedFile: true,
enableCodeFix: true,
enableCodeFix: mode === "write" || recursive,
editTracker,
options,
projectRoot,
pool,
recursive

@@ -1821,3 +1690,2 @@ });

editTracker.logResult();
await pool.close();
if (mode === "check" && !editTracker.isClean) {

@@ -1824,0 +1692,0 @@ system.exit(1);

import ts from 'typescript';
import { FileService } from './FileService.js';
import { EditTracker } from './EditTracker.js';
import { Vertexes } from './DependencyGraph.js';
import { WorkerPool } from './WorkerPool.js';
export declare const processFile: ({ targetFile, files, vertexes, deleteUnusedFile, enableCodeFix, options, projectRoot, }: {
targetFile: string;
vertexes: Map<string, {
to: Set<string>;
from: Set<string>;
data: {
depth: number;
};
}>;
files: Map<string, string>;
deleteUnusedFile: boolean;
enableCodeFix: boolean;
options: ts.CompilerOptions;
projectRoot: string;
}) => {
operation: "edit";
content: string;
removedExports: {
fileName: string;
position: number;
code: string;
}[];
} | {
operation: "delete";
};
export declare const edit: ({ entrypoints, fileService, deleteUnusedFile, enableCodeFix, editTracker, options, projectRoot, pool, recursive, }: {
export declare const edit: ({ entrypoints, fileService, deleteUnusedFile, enableCodeFix, editTracker, options, projectRoot, recursive, }: {
entrypoints: string[];

@@ -40,27 +13,2 @@ fileService: FileService;

recursive: boolean;
pool?: WorkerPool<({ targetFile, files, vertexes, deleteUnusedFile, enableCodeFix, options, projectRoot, }: {
targetFile: string;
vertexes: Map<string, {
to: Set<string>;
from: Set<string>;
data: {
depth: number;
};
}>;
files: Map<string, string>;
deleteUnusedFile: boolean;
enableCodeFix: boolean;
options: ts.CompilerOptions;
projectRoot: string;
}) => {
operation: "edit";
content: string;
removedExports: {
fileName: string;
position: number;
code: string;
}[];
} | {
operation: "delete";
}> | undefined;
}) => Promise<void>;

@@ -1,4 +0,3 @@

export declare const memoize: <T extends (...args: any[]) => any>(fn: T, { key, name }: {
export declare const memoize: <T extends (...args: any[]) => any>(fn: T, { key }: {
key: (...args: Parameters<T>) => string;
name: string;
}) => (...args: Parameters<T>) => ReturnType<T>;

@@ -114,2 +114,14 @@ import ts from 'typescript';

start: number;
} | {
kind: ts.SyntaxKind.EnumDeclaration;
name: string;
change: {
code: string;
span: {
start: number;
length: number;
};
};
skip: boolean;
start: number;
};

@@ -116,0 +128,0 @@ type AmbientDeclaration = {

@@ -44,3 +44,3 @@ {

},
"version": "0.9.0"
"version": "0.10.0"
}

@@ -8,2 +8,3 @@ <h1 align="center">ts-remove-unused</h1>

[![npm version](https://badge.fury.io/js/@line%2Fts-remove-unused.svg)](https://badge.fury.io/js/@line%2Fts-remove-unused)
[![install size](https://packagephobia.com/badge?p=ts-remove-unused)](https://packagephobia.com/result?p=ts-remove-unused)
[![CI](https://github.com/line/ts-remove-unused/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/line/ts-remove-unused/actions/workflows/ci.yml)

@@ -42,3 +43,3 @@

Here are some examples of how this tool will auto-fixe unused code.
Here are some examples of how this tool will auto-fix unused code.

@@ -127,3 +128,3 @@ <!-- prettier-ignore-start -->

Options:
--project <file> Path to your tsconfig.json
-p, --project <file> Path to your tsconfig.json
--skip <regexp_pattern> Specify the regexp pattern to match files that should be skipped from transforming

@@ -138,3 +139,3 @@ --include-d-ts Include .d.ts files in target for transformation

#### `--project`
#### `-p`, `--project`

@@ -144,3 +145,3 @@ Specifies the `tsconfig.json` that is used to analyze your codebase. Defaults to `tsconfig.json` in your project root.

```bash
npx @line/ts-remove-unused --skip tsconfig.client.json
npx @line/ts-remove-unused --project tsconfig.client.json
```

@@ -168,3 +169,3 @@

#### `-r, --recursive`
#### `-r`, `--recursive`

@@ -171,0 +172,0 @@ The default behavior of the CLI is to process all files once. Some issues may not be detected if the unused code is a result of the modification of another file in the project. When this option is enabled, ts-remove-unused will recursively re-edit/re-check files that may be affected by a file edit.

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚑️ by Socket Inc