ts-analyzer 1.3.0 → 1.3.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-analyzer",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "repository": "github:amir-valizadeh/ts-analyzer",
5
5
  "description": "Comprehensive TypeScript code analyzer with type safety and complexity metrics",
6
6
  "main": "dist/src/index.js",
@@ -59,4 +59,55 @@ describe('generateHtmlReport', () => {
59
59
  expect(html).toContain('TypeScript Safety');
60
60
  expect(html).toContain('Code Complexity');
61
61
  });
62
+ it('should generate an HTML string without optional stats (branch coverage)', () => {
63
+ const mockStats: Partial<ProjectStats> = {
64
+ files: 0,
65
+ totalLines: 0,
66
+ codeLines: 0,
67
+ commentLines: 0,
68
+ emptyLines: 0,
69
+ formatNumber: (n) => n.toString(),
70
+ formattedFileTypes: []
71
+ };
72
+ const html = generateHtmlReport(mockStats as ProjectStats);
73
+ expect(html).not.toContain('TypeScript Safety');
74
+ expect(html).not.toContain('Code Complexity');
75
+ });
76
+
77
+ it('should cover warning and danger branches for stats', () => {
78
+ const mockStats: Partial<ProjectStats> = {
79
+ files: 10,
80
+ totalLines: 1000,
81
+ codeLines: 800,
82
+ commentLines: 100,
83
+ emptyLines: 100,
84
+ formatNumber: (n) => n.toString(),
85
+ formattedFileTypes: [],
86
+ typescriptSafety: {
87
+ tsFileCount: 1, tsPercentage: '100', avgTypeCoverage: '50', // < 80 triggers warning
88
+ totalAnyCount: 15, // > 10 triggers danger
89
+ totalAssertions: 0, totalNonNullAssertions: 0, avgTypeSafetyScore: 50, overallComplexity: 'High'
90
+ },
91
+ codeComplexity: {
92
+ analyzedFiles: 1, avgComplexity: '10', // > 5 triggers warning
93
+ maxComplexity: 20, // > 15 triggers danger
94
+ avgNestingDepth: '1.5', maxNestingDepth: 3, avgFunctionSize: '20', totalFunctions: 1,
95
+ complexFiles: 1, // > 0 triggers danger
96
+ overallComplexity: 'High',
97
+ codeSmells: {
98
+ magicNumbers: 5, // > 0
99
+ callbackHell: 5, // > 0
100
+ godFiles: 5, // > 0
101
+ excessiveParameters: 5 // > 0
102
+ }
103
+ }
104
+ };
105
+
106
+ const html = generateHtmlReport(mockStats as ProjectStats);
107
+ expect(html).toContain('warning');
108
+ expect(html).toContain('#ffc107'); // magicNumbers uses warning color
109
+ expect(html).toContain('#dc3545'); // danger color
110
+ });
62
111
  });
112
+
113
+
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
2
+ import { analyzeProject } from '../src/index.js';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+
6
+ describe('analyzeProject', () => {
7
+ const testDir = path.join(process.cwd(), 'temp_index_test_dir');
8
+
9
+ beforeAll(async () => {
10
+ await fs.mkdir(testDir, { recursive: true });
11
+ await fs.writeFile(path.join(testDir, 'file1.ts'), 'const a = 1;\nconsole.log(a);');
12
+ await fs.writeFile(path.join(testDir, 'file1_duplicate.ts'), 'const b = 2;');
13
+ await fs.writeFile(path.join(testDir, 'file2.js'), '// comment\nlet b = 2;');
14
+ await fs.writeFile(path.join(testDir, 'ignored.txt'), 'not counted');
15
+
16
+ const subDir = path.join(testDir, 'subdir');
17
+ await fs.mkdir(subDir, { recursive: true });
18
+ await fs.writeFile(path.join(subDir, 'file3.css'), 'body { margin: 0; }');
19
+
20
+ const ignoreDir = path.join(testDir, 'node_modules');
21
+ await fs.mkdir(ignoreDir, { recursive: true });
22
+ await fs.writeFile(path.join(ignoreDir, 'lib.ts'), 'const ignored = true;');
23
+ });
24
+
25
+ afterAll(async () => {
26
+ await fs.rm(testDir, { recursive: true, force: true });
27
+ });
28
+
29
+ it('should correctly analyze a project directory with default options', async () => {
30
+ const stats = await analyzeProject(testDir, {
31
+ analyzeSafety: false,
32
+ analyzeComplexity: false
33
+ });
34
+
35
+ expect(stats.files).toBe(4); // file1.ts, file1_duplicate.ts, file2.js, file3.css
36
+ expect(stats.fileTypes['.ts']).toBeDefined();
37
+ expect(stats.fileTypes['.js']).toBeDefined();
38
+ expect(stats.fileTypes['.css']).toBeDefined();
39
+ expect(stats.typescriptSafety).toBeNull();
40
+ expect(stats.codeComplexity).toBeNull();
41
+ });
42
+
43
+ it('should include safety and complexity when requested', async () => {
44
+ const stats = await analyzeProject(testDir, {
45
+ analyzeSafety: true,
46
+ analyzeComplexity: true
47
+ });
48
+
49
+ expect(stats.typescriptSafety).not.toBeNull();
50
+ expect(stats.codeComplexity).not.toBeNull();
51
+ });
52
+
53
+ it('should respect custom ignore options and extra extensions', async () => {
54
+ const stats = await analyzeProject(testDir, {
55
+ excludePatterns: ['subdir'],
56
+ additionalExtensions: ['.txt'],
57
+ analyzeSafety: false,
58
+ analyzeComplexity: false
59
+ });
60
+
61
+ expect(stats.files).toBe(4); // file1.ts, file1_duplicate.ts, file2.js, ignored.txt
62
+ // subdir is excluded, so file3.css is skipped
63
+ expect(stats.fileTypes['.css']).toBeUndefined();
64
+ expect(stats.fileTypes['.txt']).toBeDefined();
65
+ });
66
+ });
@@ -42,4 +42,10 @@ const b = 2;
42
42
  expect(stats.total).toBe(1); // fs.readFile('...') on empty string often gives 1 line array [""]
43
43
  expect(stats.code).toBe(0);
44
44
  });
45
+
46
+ it('should handle reading errors gracefully', async () => {
47
+ const stats = await countLines('/invalid/path/that/does/not/exist.ts');
48
+ expect(stats.total).toBe(0);
49
+ expect(stats.code).toBe(0);
50
+ });
45
51
  });
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, afterEach } from 'vitest';
2
- import { analyzeTypeScriptSafety } from '../src/typescript-safety.js';
2
+ import { analyzeTypeScriptSafety, calculateProjectTypeSafety } from '../src/typescript-safety.js';
3
3
  import fs from 'fs/promises';
4
4
  import path from 'path';
5
5
 
@@ -47,4 +47,62 @@ const b = <string>"foo";
47
47
 
48
48
  expect(metrics.typeAssertions).toBeGreaterThan(0);
49
49
  });
50
+ it('should detect complex types, interfaces, non-null assertions, and generics', async () => {
51
+ const content = `
52
+ interface MyInterface<T> {
53
+ value?: T;
54
+ }
55
+ const arr: Array<string> = [];
56
+ const obj: MyInterface<number> = {};
57
+ const val = obj.value!;
58
+ const func = <U>(arg: U) => arg;
59
+ class MyClass {
60
+ prop = 123;
61
+ method() { return this.prop; }
62
+ }
63
+ export type MyType = string | number;
64
+ import { something } from "somewhere";
65
+ `;
66
+ await fs.writeFile(testFilePath, content);
67
+ const metrics = await analyzeTypeScriptSafety(testFilePath);
68
+
69
+ expect(metrics.nonNullAssertions).toBeGreaterThan(0);
70
+ expect(metrics.optionalProperties).toBeGreaterThan(0);
71
+ expect(metrics.genericsCount).toBeGreaterThan(0);
72
+ });
73
+
74
+ it('should handle un-parseable or missing files gracefully', async () => {
75
+ const metrics = await analyzeTypeScriptSafety('/non/existent/path/this/should/throw/error.ts');
76
+ expect(metrics.typeCoverage).toBe('0.0');
77
+ });
50
78
  });
79
+
80
+ describe('calculateProjectTypeSafety', () => {
81
+ it('should return default zero metrics for no TS files', async () => {
82
+ const metrics = await calculateProjectTypeSafety('.', ['index.js', 'style.css']);
83
+ expect(metrics.tsFileCount).toBe(0);
84
+ expect(metrics.avgTypeSafetyScore).toBe(0);
85
+ });
86
+
87
+ it('should calculate averages across multiple files', async () => {
88
+ // We will pass the src directory and check if it handles it.
89
+ // We can just rely on the existing files in the project or mock files.
90
+ // Let's create two temp files
91
+ const tempPath1 = path.join(process.cwd(), 'temp_s_test1.ts');
92
+ const tempPath2 = path.join(process.cwd(), 'temp_s_test2.tsx');
93
+
94
+ await fs.writeFile(tempPath1, 'const x: number = 1;');
95
+ await fs.writeFile(tempPath2, 'const y: any = 2;');
96
+
97
+ try {
98
+ const metrics = await calculateProjectTypeSafety(process.cwd(), ['temp_s_test1.ts', 'temp_s_test2.tsx']);
99
+ expect(metrics.tsFileCount).toBe(2);
100
+ expect(metrics.totalAnyCount).toBe(1);
101
+ expect(metrics.tsPercentage).toBe('100.0');
102
+ } finally {
103
+ try { await fs.unlink(tempPath1); } catch(e) {}
104
+ try { await fs.unlink(tempPath2); } catch(e) {}
105
+ }
106
+ });
107
+ });
108
+