@tng-sh/js 0.1.5 → 0.1.7

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.
package/README.md CHANGED
@@ -59,12 +59,20 @@ Add the `--json` flag to any command to receive output as structured JSON events
59
59
  npx tng -f path/to/file.js --clones --json
60
60
  ```
61
61
 
62
+ ### 4. Dead Code Analysis
63
+ Identify unreachable code, unused variables, and unused imports in your project:
64
+
65
+ ```bash
66
+ npx -p @tng-sh/js tng --deadcode -f path/to/file.js
67
+ ```
68
+
62
69
  ## Features
63
70
 
64
- - **Oxc-Powered**: Blazing fast AST analysis using the industry-leading Rust parser.
65
- - **Multi-Platform**: Native support for macOS (Intel/ARM) and Linux (x64/ARM64).
66
- - **Prettier Integration**: Automatically formats generated code to match your existing style.
67
- - **TypeScript Native**: First-class support for `.ts` and `.tsx` analysis and generation.
71
+ - **Blazing Fast Static Analysis**: Powered by Rust and the `oxc` parser for near-instant AST processing.
72
+ - **Interactive Terminal UI**: Dual-pane keyboard-driven interface for deep code exploration.
73
+ - **Smart Visualization**: Generate logic flow diagrams using Mermaid.js integration.
74
+ - **Dead Code Cleanup**: Find and remove unused imports, variables, and unreachable logic.
75
+ - **First-Class TS & React**: Native support for TypeScript, JSX, and TSX.
68
76
 
69
77
  ## ⚙️ Configuration
70
78
 
package/bin/tng.js CHANGED
@@ -27,8 +27,8 @@ process.on('uncaughtException', (err) => {
27
27
 
28
28
  program
29
29
  .name('tng')
30
- .description('TNG - Automated Test Generation, and Audit generation for JavaScript')
31
- .version('0.1.5');
30
+ .description('TNG - Advanced Code Audit, Test Generation, Visualization, and Dead Code Analysis for JavaScript/TypeScript')
31
+ .version('0.1.7');
32
32
 
33
33
  /**
34
34
  * @command init
@@ -138,7 +138,7 @@ function launchInteractive() {
138
138
  program
139
139
  .command('i')
140
140
  .alias('interactive')
141
- .description('Launch interactive UI')
141
+ .description('Launch interactive UI for code audit, test generation, and visualization')
142
142
  .action(() => {
143
143
  launchInteractive();
144
144
  });
@@ -213,6 +213,7 @@ program
213
213
  .option('--trace', 'Run symbolic trace visualization')
214
214
  .option('-c, --clones', 'Run duplicate code detection')
215
215
  .option('-l, --level <level>', 'Set clone detection level (1, 2, or all)', 'all')
216
+ .option('-d, --deadcode', 'Run dead code detection (JS/TS/React)')
216
217
  .option('--json', 'Output results as JSON events (machine-readable)')
217
218
 
218
219
  .action(async (options) => {
@@ -264,7 +265,9 @@ program
264
265
  generateTest(options.file, options.method, options.type, options.audit, options.json);
265
266
  } else if (options.file && !options.method) {
266
267
  if (options.clones) {
267
- runClones(options.file, options.level || 'all', options.json);
268
+ await runClones(options.file, options.level || 'all', options.json);
269
+ } else if (options.deadcode) {
270
+ await runDeadCode(options.file, options.json);
268
271
  } else {
269
272
  console.log(chalk.yellow('Specify a method with -m, use --outline to see methods, or run "tng i" for full selection.'));
270
273
  }
@@ -321,7 +324,58 @@ async function runClones(filePath, level, jsonMode = false) {
321
324
  });
322
325
 
323
326
  if (matches) {
324
- session.showClones(absolutePath, matches);
327
+ await session.showClones(absolutePath, matches);
328
+ }
329
+ } catch (e) {
330
+ console.log(chalk.red(`Error: ${e.message}`));
331
+ process.exit(1);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Logic to run dead code detection
337
+ */
338
+ async function runDeadCode(filePath, jsonMode = false) {
339
+ const { detectDeadCode } = require('../index');
340
+ const absolutePath = path.resolve(filePath);
341
+
342
+ if (!fs.existsSync(absolutePath)) {
343
+ console.log(chalk.red(`File not found: ${filePath}`));
344
+ process.exit(1);
345
+ }
346
+
347
+ const GoUISession = require('../lib/goUiSession');
348
+ const session = new GoUISession();
349
+
350
+ if (jsonMode) {
351
+ const { JsonSession } = require('../lib/jsonSession');
352
+ const jsonSession = new JsonSession();
353
+ jsonSession.start();
354
+ try {
355
+ const resultJson = detectDeadCode(absolutePath);
356
+ jsonSession.emitEvent('dead_code', JSON.parse(resultJson));
357
+ jsonSession.stop();
358
+ } catch (e) {
359
+ jsonSession.displayError(e.message);
360
+ jsonSession.stop();
361
+ process.exit(1);
362
+ }
363
+ return;
364
+ }
365
+
366
+ console.log(chalk.blue(`🔍 Analyzing dead code in ${filePath}...`));
367
+
368
+ try {
369
+ const issuesJson = await session.showProgress('Analyzing dead code...', async (progress) => {
370
+ progress.update('Parsing AST...', { percent: 30 });
371
+ const resultJson = detectDeadCode(absolutePath);
372
+ progress.update('Identifying issues...', { percent: 70 });
373
+ progress.update('Done!', { percent: 100 });
374
+ return resultJson;
375
+ });
376
+
377
+ if (issuesJson) {
378
+ await session.showDeadCodeResults(absolutePath, issuesJson);
325
379
  }
326
380
  } catch (e) {
327
381
  console.log(chalk.red(`Error: ${e.message}`));
Binary file
Binary file
Binary file
Binary file
package/index.d.ts CHANGED
@@ -61,3 +61,5 @@ export declare function getSymbolicTrace(filePath: string, methodName: string, c
61
61
  * Returns a JSON string containing the matches found.
62
62
  */
63
63
  export declare function analyzeClones(projectRoot: string, filePath: string, level: string): string
64
+ /** Analyzes a file for dead code. */
65
+ export declare function detectDeadCode(filePath: string): string
Binary file
Binary file
package/index.js CHANGED
@@ -310,7 +310,7 @@ if (!nativeBinding) {
310
310
  throw new Error(`Failed to load native binding`)
311
311
  }
312
312
 
313
- const { getFileOutline, getProjectMetadata, findCallSites, ping, submitJob, getUserStats, runAudit, applyEdit, applyEditsAtomic, generateTest, getSymbolicTrace, analyzeClones } = nativeBinding
313
+ const { getFileOutline, getProjectMetadata, findCallSites, ping, submitJob, getUserStats, runAudit, applyEdit, applyEditsAtomic, generateTest, getSymbolicTrace, analyzeClones, detectDeadCode } = nativeBinding
314
314
 
315
315
  module.exports.getFileOutline = getFileOutline
316
316
  module.exports.getProjectMetadata = getProjectMetadata
@@ -324,3 +324,4 @@ module.exports.applyEditsAtomic = applyEditsAtomic
324
324
  module.exports.generateTest = generateTest
325
325
  module.exports.getSymbolicTrace = getSymbolicTrace
326
326
  module.exports.analyzeClones = analyzeClones
327
+ module.exports.detectDeadCode = detectDeadCode
Binary file
Binary file
@@ -43,6 +43,9 @@ class GenerateTestsUI {
43
43
  } else if (choice === 'clones') {
44
44
  const result = await this._showClonesSelection();
45
45
  if (result === 'exit') return 'exit';
46
+ } else if (choice === 'deadcode') {
47
+ const result = await this._showDeadCodeSelection();
48
+ if (result === 'exit') return 'exit';
46
49
  } else if (choice === 'stats') {
47
50
  await this._showStats();
48
51
  } else if (choice === 'about') {
@@ -761,7 +764,55 @@ class GenerateTestsUI {
761
764
  });
762
765
 
763
766
  if (matches) {
764
- this.goUiSession.showClones(absolutePath, matches);
767
+ await this.goUiSession.showClones(absolutePath, matches);
768
+ }
769
+ } catch (e) {
770
+ console.log(chalk.red(`Error: ${e.message}`));
771
+ }
772
+ }
773
+
774
+ async _showDeadCodeSelection() {
775
+ const files = await this._getUserFiles();
776
+
777
+ if (files.length === 0) {
778
+ console.log(chalk.yellow('\nNo JavaScript or TypeScript files found in your project.\n'));
779
+ return 'back';
780
+ }
781
+
782
+ const cwd = process.cwd();
783
+ const items = files.map(file => ({
784
+ name: path.relative(cwd, file),
785
+ path: path.dirname(file)
786
+ }));
787
+
788
+ const selectedName = this.goUiSession.showListView('Select File for Dead Code Detection', items);
789
+
790
+ if (selectedName === 'back') return 'back';
791
+ if (!selectedName || selectedName === 'exit') return 'exit';
792
+
793
+ const selectedFile = path.resolve(cwd, selectedName);
794
+ await this._runDeadCode(selectedFile);
795
+ return 'main_menu';
796
+ }
797
+
798
+ async _runDeadCode(filePath) {
799
+ const { detectDeadCode } = require('../index');
800
+ const absolutePath = path.resolve(filePath);
801
+ const projectRoot = process.cwd();
802
+
803
+ console.log(chalk.blue(`🔍 Analyzing dead code in ${path.relative(projectRoot, filePath)}...`));
804
+
805
+ try {
806
+ const issuesJson = await this.goUiSession.showProgress('Analyzing dead code...', async (progress) => {
807
+ progress.update('Parsing AST...', { percent: 30 });
808
+ const resultJson = detectDeadCode(absolutePath);
809
+ progress.update('Identifying issues...', { percent: 70 });
810
+ progress.update('Done!', { percent: 100 });
811
+ return resultJson;
812
+ });
813
+
814
+ if (issuesJson) {
815
+ await this.goUiSession.showDeadCodeResults(absolutePath, issuesJson);
765
816
  }
766
817
  } catch (e) {
767
818
  console.log(chalk.red(`Error: ${e.message}`));
@@ -369,6 +369,35 @@ class GoUISession {
369
369
  }
370
370
  }
371
371
 
372
+ async showDeadCodeResults(filePath, resultsJson) {
373
+ const issues = JSON.parse(resultsJson);
374
+ const data = {
375
+ file: filePath,
376
+ dead_code: issues.map(issue => ({
377
+ type: issue.issue_type,
378
+ line: issue.line,
379
+ message: issue.message,
380
+ code: issue.code_snippet
381
+ }))
382
+ };
383
+ const inputFile = this._trackTempFile(this._createTempFile('dead-code-data', '.json'));
384
+
385
+ try {
386
+ fs.writeFileSync(inputFile, JSON.stringify(data));
387
+
388
+ spawnSync(this._binaryPath, ['deadcode', '--file', inputFile], {
389
+ stdio: 'inherit',
390
+ env: process.env
391
+ });
392
+ } catch (error) {
393
+ console.error('Dead code results display error:', error.message);
394
+ } finally {
395
+ this._cleanupTempFile(inputFile);
396
+ }
397
+ }
398
+
399
+
400
+
372
401
  async showStreamingAuditResults(methodName, className, sourceCode) {
373
402
  const outputFile = this._trackTempFile(this._createTempFile('audit-choice', '.txt'));
374
403
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tng-sh/js",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "TNG JavaScript CLI",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1 +0,0 @@
1
- {"file_path":"/Users/claudiu/work/tng-inc/js-pie/test_duplicates.js","matches":[{"start1":1,"end1":13,"start2":16,"end2":28,"line_count":13,"node_count":12,"complexity_score":245,"algo":"structural"}]}
@@ -1 +0,0 @@
1
- {"type":"step","step":1,"message":"Done!","percent":100}
@@ -1,13 +0,0 @@
1
- tng_sh_js:{"kind": "interface", "name": "CloneMatch", "js_doc": "", "def": "start1: number\nend1: number\nstart2: number\nend2: number\nlineCount: number\nnodeCount: number\ncomplexityScore: number\nalgo: string", "original_name": "CloneMatch"}
2
- tng_sh_js:{"kind": "fn", "name": "getFileOutline", "js_doc": "/**\n * Analyzes a JavaScript/TypeScript file and returns its structural outline as a JSON string.\n * Contains information about classes and methods found in the file.\n */\n", "def": "export declare function getFileOutline(filePath: string): string"}
3
- tng_sh_js:{"kind": "fn", "name": "getProjectMetadata", "js_doc": "/**\n * Gathers project-wide metadata from package.json and related files.\n * Returns a JSON string containing dependencies, frameworks, and project type.\n */\n", "def": "export declare function getProjectMetadata(projectRoot: string): string"}
4
- tng_sh_js:{"kind": "fn", "name": "findCallSites", "js_doc": "/**\n * Searches the project for all locations where a specific method is called.\n * Uses ripgrep under the hood for high performance.\n */\n", "def": "export declare function findCallSites(projectRoot: string, methodName: string): string"}
5
- tng_sh_js:{"kind": "fn", "name": "ping", "js_doc": "/** Pings the TNG API to verify connectivity and API key validity. */\n", "def": "export declare function ping(baseUrl: string, apiKey?: string | undefined | null): string"}
6
- tng_sh_js:{"kind": "fn", "name": "submitJob", "js_doc": "/** Submits a test generation job to the API and returns the numeric job ID. */\n", "def": "export declare function submitJob(baseUrl: string, apiKey: string, payloadJson: string): number"}
7
- tng_sh_js:{"kind": "fn", "name": "getUserStats", "js_doc": "/** Fetches usage statistics for the authenticated user from the API. */\n", "def": "export declare function getUserStats(baseUrl: string, apiKey: string): string"}
8
- tng_sh_js:{"kind": "fn", "name": "runAudit", "js_doc": "/**\n * Orchestrates the code audit process.\n * Analyzes the source, builds context, and streams results via the provided callback.\n */\n", "def": "export declare function runAudit(filePath: string, methodName: string, className: string | undefined | null, testType: string | undefined | null, configJson: string, callback: (...args: any[]) => any): string"}
9
- tng_sh_js:{"kind": "fn", "name": "applyEdit", "js_doc": "/**\n * Applies a single edit operation to a file.\n * Uses backup and atomic write for safety.\n */\n", "def": "export declare function applyEdit(filePath: string, search: string, replace: string, lineHint?: number | undefined | null): string"}
10
- tng_sh_js:{"kind": "fn", "name": "applyEditsAtomic", "js_doc": "/**\n * Applies multiple edit operations atomically.\n * All succeed or all fail (with rollback).\n */\n", "def": "export declare function applyEditsAtomic(operationsJson: string): string"}
11
- tng_sh_js:{"kind": "fn", "name": "generateTest", "js_doc": "/**\n * Orchestrates the test generation process.\n * Analyzes code, submits a job, and polls for the final generated test.\n */\n", "def": "export declare function generateTest(filePath: string, methodName: string, className: string | undefined | null, testType: string | undefined | null, configJson: string, callback: (...args: any[]) => any): string"}
12
- tng_sh_js:{"kind": "fn", "name": "getSymbolicTrace", "js_doc": "/** Analyzes a method and generates a symbolic execution trace. */\n", "def": "export declare function getSymbolicTrace(filePath: string, methodName: string, className?: string | undefined | null): string"}
13
- tng_sh_js:{"kind": "fn", "name": "analyzeClones", "js_doc": "/**\n * Analyzes a file for code clones (duplicates).\n * Returns a JSON string containing the matches found.\n */\n", "def": "export declare function analyzeClones(projectRoot: string, filePath: string, level: string): string"}
@@ -1,13 +0,0 @@
1
- tng_sh_js: __napi_register__CloneMatch_struct_0
2
- tng_sh_js: __napi_register__get_file_outline_1
3
- tng_sh_js: __napi_register__get_project_metadata_2
4
- tng_sh_js: __napi_register__find_call_sites_3
5
- tng_sh_js: __napi_register__ping_4
6
- tng_sh_js: __napi_register__submit_job_5
7
- tng_sh_js: __napi_register__get_user_stats_6
8
- tng_sh_js: __napi_register__run_audit_7
9
- tng_sh_js: __napi_register__apply_edit_8
10
- tng_sh_js: __napi_register__apply_edits_atomic_9
11
- tng_sh_js: __napi_register__generate_test_10
12
- tng_sh_js: __napi_register__get_symbolic_trace_11
13
- tng_sh_js: __napi_register__analyze_clones_12
package/out.log DELETED
File without changes