find-duplicate-js 1.1.0 → 1.2.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.2.0] - 2025-12-31
6
+
7
+ ### 🚀 New Features
8
+
9
+ #### Programmatic API Support
10
+ - **Export Functions**: Package now exports core functions for programmatic use
11
+ - `findDuplicates` - Main function to find duplicate code
12
+ - `findJsFiles` - Find all JavaScript files in a directory
13
+ - `extractFunctions` - Extract functions from code
14
+ - `normalizeCode` - Normalize code for comparison
15
+ - `calculateSimilarity` - Calculate similarity between code snippets
16
+ - **Usage Example**:
17
+ ```javascript
18
+ import { findDuplicates, findJsFiles } from 'find-duplicate-js';
19
+
20
+ const result = findDuplicates('./src', 70);
21
+ console.log(result);
22
+ ```
23
+
24
+ ### ✨ Improvements
25
+
26
+ #### UI Code Refactoring
27
+ - **Separated Concerns**: Split HTML, CSS, and JavaScript into separate files
28
+ - `ui-template.html` - HTML structure
29
+ - `ui-styles.css` - All styling
30
+ - `find-duplicates-ui.js` - Logic only
31
+ - **Better Maintainability**: Easier to update and customize the UI
32
+ - **Cleaner Code**: More readable and organized codebase
33
+
34
+ ### 🐛 Bug Fixes
35
+ - Fixed module export issue that prevented importing functions from the package
36
+ - Resolved "does not provide an export named 'findDuplicates'" error
37
+
5
38
  ## [1.1.0] - 2025-12-30
6
39
 
7
40
  ### 🐛 Bug Fixes
@@ -2,8 +2,14 @@
2
2
 
3
3
  import { exec as open } from "child_process";
4
4
  import http from "http";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import { fileURLToPath } from "url";
5
8
  import { findDuplicates, findJsFiles } from "./find-duplicates-core.js";
6
9
 
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+
7
13
  const PORT = 2712;
8
14
 
9
15
  /**
@@ -14,315 +20,68 @@ const PORT = 2712;
14
20
  * @description Creates a responsive, interactive web interface with statistics dashboard and side-by-side code comparison
15
21
  */
16
22
  function generateHTML(duplicates, stats) {
17
- return `<!DOCTYPE html>
18
- <html lang="en">
19
- <head>
20
- <meta charset="UTF-8">
21
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
22
- <title>JavaScript Duplicate Finder</title>
23
- <style>
24
- * {
25
- margin: 0;
26
- padding: 0;
27
- box-sizing: border-box;
28
- }
29
-
30
- body {
31
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
32
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
33
- min-height: 100vh;
34
- padding: 20px;
35
- }
36
-
37
- .container {
38
- max-width: 1200px;
39
- margin: 0 auto;
40
- }
41
-
42
- .header {
43
- background: white;
44
- padding: 30px;
45
- border-radius: 15px;
46
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
47
- margin-bottom: 30px;
48
- text-align: center;
49
- }
50
-
51
- .header h1 {
52
- color: #667eea;
53
- font-size: 2.5em;
54
- margin-bottom: 10px;
55
- }
56
-
57
- .header p {
58
- color: #666;
59
- font-size: 1.1em;
60
- }
61
-
62
- .stats {
63
- display: grid;
64
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
65
- gap: 20px;
66
- margin-bottom: 30px;
67
- }
68
-
69
- .stat-card {
70
- background: white;
71
- padding: 25px;
72
- border-radius: 12px;
73
- box-shadow: 0 5px 15px rgba(0,0,0,0.1);
74
- text-align: center;
75
- }
76
-
77
- .stat-card .number {
78
- font-size: 2.5em;
79
- font-weight: bold;
80
- color: #667eea;
81
- margin-bottom: 5px;
82
- }
83
-
84
- .stat-card .label {
85
- color: #666;
86
- font-size: 0.9em;
87
- text-transform: uppercase;
88
- letter-spacing: 1px;
89
- }
90
-
91
- .no-duplicates {
92
- background: white;
93
- padding: 60px;
94
- border-radius: 15px;
95
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
96
- text-align: center;
97
- }
98
-
99
- .no-duplicates .icon {
100
- font-size: 5em;
101
- margin-bottom: 20px;
102
- }
103
-
104
- .no-duplicates h2 {
105
- color: #4CAF50;
106
- font-size: 2em;
107
- margin-bottom: 10px;
108
- }
109
-
110
- .duplicate-card {
111
- background: white;
112
- border-radius: 15px;
113
- box-shadow: 0 5px 20px rgba(0,0,0,0.1);
114
- margin-bottom: 25px;
115
- overflow: hidden;
116
- transition: transform 0.3s ease, box-shadow 0.3s ease;
117
- }
118
-
119
- .duplicate-card:hover {
120
- transform: translateY(-5px);
121
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
122
- }
123
-
124
- .duplicate-header {
125
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
126
- color: white;
127
- padding: 20px 30px;
128
- display: flex;
129
- justify-content: space-between;
130
- align-items: center;
131
- }
132
-
133
- .duplicate-header h3 {
134
- font-size: 1.3em;
135
- }
136
-
137
- .similarity-badge {
138
- background: rgba(255,255,255,0.2);
139
- padding: 8px 20px;
140
- border-radius: 20px;
141
- font-weight: bold;
142
- font-size: 1.2em;
143
- }
144
-
145
- .duplicate-body {
146
- padding: 30px;
147
- }
148
-
149
- .function-comparison {
150
- display: grid;
151
- grid-template-columns: 1fr 1fr;
152
- gap: 30px;
153
- }
154
-
155
- .function-info {
156
- background: #f8f9fa;
157
- padding: 20px;
158
- border-radius: 10px;
159
- border-left: 4px solid #667eea;
160
- }
161
-
162
- .function-info h4 {
163
- color: #667eea;
164
- margin-bottom: 10px;
165
- font-size: 1.1em;
166
- }
167
-
168
- .file-path {
169
- color: #666;
170
- font-size: 0.85em;
171
- margin-bottom: 8px;
172
- word-break: break-all;
173
- }
174
-
175
- .function-name {
176
- font-family: 'Courier New', monospace;
177
- background: #e3f2fd;
178
- padding: 5px 10px;
179
- border-radius: 5px;
180
- display: inline-block;
181
- margin-bottom: 15px;
182
- color: #1976d2;
183
- font-weight: bold;
184
- }
185
-
186
- .code-preview {
187
- background: #263238;
188
- color: #aed581;
189
- padding: 15px;
190
- border-radius: 8px;
191
- font-family: 'Courier New', monospace;
192
- font-size: 0.9em;
193
- overflow-x: auto;
194
- white-space: pre-wrap;
195
- word-break: break-all;
196
- max-height: 200px;
197
- overflow-y: auto;
198
- }
199
-
200
- @media (max-width: 768px) {
201
- .function-comparison {
202
- grid-template-columns: 1fr;
203
- }
204
-
205
- .header h1 {
206
- font-size: 1.8em;
207
- }
208
- }
209
-
210
- .refresh-btn {
211
- background: white;
212
- color: #667eea;
213
- border: 2px solid #667eea;
214
- padding: 12px 30px;
215
- border-radius: 25px;
216
- font-size: 1em;
217
- font-weight: bold;
218
- cursor: pointer;
219
- transition: all 0.3s ease;
220
- margin-top: 20px;
221
- }
222
-
223
- .refresh-btn:hover {
224
- background: #667eea;
225
- color: white;
226
- transform: scale(1.05);
227
- }
228
- </style>
229
- </head>
230
- <body>
231
- <div class="container">
232
- <div class="header">
233
- <h1>🔍 JavaScript Duplicate Finder</h1>
234
- <p>Intelligent code duplication detection</p>
235
- <button class="refresh-btn" onclick="location.reload()">🔄 Refresh Analysis</button>
236
- </div>
237
-
238
- <div class="stats">
239
- <div class="stat-card">
240
- <div class="number">${stats.filesScanned}</div>
241
- <div class="label">Files Scanned</div>
242
- </div>
243
- <div class="stat-card">
244
- <div class="number">${stats.functionsFound}</div>
245
- <div class="label">Functions Found</div>
246
- </div>
247
- <div class="stat-card">
248
- <div class="number">${stats.duplicatesFound}</div>
249
- <div class="label">Duplicate Pairs</div>
250
- </div>
251
- <div class="stat-card">
252
- <div class="number">${stats.threshold}%</div>
253
- <div class="label">Threshold</div>
254
- </div>
255
- </div>
256
-
257
- ${
258
- duplicates.length === 0
259
- ? `
260
- <div class="no-duplicates">
261
- <div class="icon">✅</div>
262
- <h2>Great! No Duplicates Found</h2>
263
- <p>Your code is clean and well-organized.</p>
264
- </div>
265
- `
266
- : `
267
- ${duplicates
268
- .map(
269
- (dup, index) => `
270
- <div class="duplicate-card">
271
- <div class="duplicate-header">
272
- <h3>📋 Match #${index + 1}</h3>
273
- <div class="similarity-badge">${
274
- dup.similarity
275
- }% Similar</div>
276
- </div>
277
- <div class="duplicate-body">
278
- <div class="function-comparison">
279
- <div class="function-info">
280
- <h4>Function 1</h4>
281
- <div class="file-path">📁 ${
282
- dup.func1.filePath
283
- }</div>
284
- <div class="function-name">${
285
- dup.func1.name
286
- }()</div>
287
- <div class="code-preview">${escapeHtml(
288
- dup.func1.originalBody.substring(0, 200)
289
- )}${
290
- dup.func1.originalBody.length > 200 ? "..." : ""
291
- }</div>
292
- </div>
293
- <div class="function-info">
294
- <h4>Function 2</h4>
295
- <div class="file-path">📁 ${
296
- dup.func2.filePath
297
- }</div>
298
- <div class="function-name">${
299
- dup.func2.name
300
- }()</div>
301
- <div class="code-preview">${escapeHtml(
302
- dup.func2.originalBody.substring(0, 200)
303
- )}${
304
- dup.func2.originalBody.length > 200 ? "..." : ""
305
- }</div>
306
- </div>
307
- </div>
308
- </div>
309
- </div>
310
- `
311
- )
312
- .join("")}
23
+ const templatePath = path.join(__dirname, "ui-template.html");
24
+ const cssPath = path.join(__dirname, "ui-styles.css");
25
+
26
+ let template = fs.readFileSync(templatePath, "utf-8");
27
+ const css = fs.readFileSync(cssPath, "utf-8");
28
+
29
+ // Inject CSS inline
30
+ template = template.replace('<link rel="stylesheet" href="ui-styles.css">', `<style>${css}</style>`);
31
+
32
+ // Replace stats placeholders
33
+ template = template
34
+ .replace("{{filesScanned}}", stats.filesScanned)
35
+ .replace("{{functionsFound}}", stats.functionsFound)
36
+ .replace("{{duplicatesFound}}", stats.duplicatesFound)
37
+ .replace("{{threshold}}", stats.threshold);
38
+
39
+ // Generate results HTML
40
+ const resultsHTML = duplicates.length === 0
41
+ ? `
42
+ <div class="no-duplicates">
43
+ <div class="icon">✅</div>
44
+ <h2>Great! No Duplicates Found</h2>
45
+ <p>Your code is clean and well-organized.</p>
46
+ </div>
47
+ `
48
+ : duplicates
49
+ .map(
50
+ (dup, index) => `
51
+ <div class="duplicate-card">
52
+ <div class="duplicate-header">
53
+ <h3>📋 Match #${index + 1}</h3>
54
+ <div class="similarity-badge">${dup.similarity}% Similar</div>
55
+ </div>
56
+ <div class="duplicate-body">
57
+ <div class="function-comparison">
58
+ <div class="function-info">
59
+ <h4>Function 1</h4>
60
+ <div class="file-path">📁 ${dup.func1.filePath}</div>
61
+ <div class="function-name">${dup.func1.name}()</div>
62
+ <div class="code-preview">${escapeHtml(
63
+ dup.func1.originalBody.substring(0, 200)
64
+ )}${dup.func1.originalBody.length > 200 ? "..." : ""}</div>
65
+ </div>
66
+ <div class="function-info">
67
+ <h4>Function 2</h4>
68
+ <div class="file-path">📁 ${dup.func2.filePath}</div>
69
+ <div class="function-name">${dup.func2.name}()</div>
70
+ <div class="code-preview">${escapeHtml(
71
+ dup.func2.originalBody.substring(0, 200)
72
+ )}${dup.func2.originalBody.length > 200 ? "..." : ""}</div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
313
77
  `
314
- }
315
- </div>
316
-
317
- <script>
318
- function escapeHtml(text) {
319
- const div = document.createElement('div');
320
- div.textContent = text;
321
- return div.innerHTML;
322
- }
323
- </script>
324
- </body>
325
- </html>`;
78
+ )
79
+ .join("");
80
+
81
+ // Inject results
82
+ template = template.replace('<div id="results">', `<div id="results">${resultsHTML}`);
83
+
84
+ return template;
326
85
  }
327
86
 
328
87
  /**
@@ -54,3 +54,6 @@ const result = findDuplicates(directory, threshold);
54
54
  console.log(`📊 Found ${result.totalFunctions} functions total\n`);
55
55
 
56
56
  displayResults(result);
57
+
58
+ // Export functions for programmatic use
59
+ export { findDuplicates, findJsFiles, extractFunctions, normalizeCode, calculateSimilarity } from './find-duplicates-core.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "find-duplicate-js",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A tool to find duplicate code in JavaScript functions",
5
5
  "main": "find-duplicates.js",
6
6
  "type": "module",
@@ -42,6 +42,8 @@
42
42
  "find-duplicates.js",
43
43
  "find-duplicates-core.js",
44
44
  "find-duplicates-ui.js",
45
+ "ui-template.html",
46
+ "ui-styles.css",
45
47
  "README.md",
46
48
  "CHANGELOG.md",
47
49
  "LICENSE"
package/ui-styles.css ADDED
@@ -0,0 +1,204 @@
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
10
+ min-height: 100vh;
11
+ padding: 20px;
12
+ }
13
+
14
+ .container {
15
+ max-width: 1200px;
16
+ margin: 0 auto;
17
+ }
18
+
19
+ .header {
20
+ background: white;
21
+ padding: 30px;
22
+ border-radius: 15px;
23
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
24
+ margin-bottom: 30px;
25
+ text-align: center;
26
+ }
27
+
28
+ .header h1 {
29
+ color: #667eea;
30
+ font-size: 2.5em;
31
+ margin-bottom: 10px;
32
+ }
33
+
34
+ .header p {
35
+ color: #666;
36
+ font-size: 1.1em;
37
+ }
38
+
39
+ .stats {
40
+ display: grid;
41
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
42
+ gap: 20px;
43
+ margin-bottom: 30px;
44
+ }
45
+
46
+ .stat-card {
47
+ background: white;
48
+ padding: 25px;
49
+ border-radius: 12px;
50
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
51
+ text-align: center;
52
+ }
53
+
54
+ .stat-card .number {
55
+ font-size: 2.5em;
56
+ font-weight: bold;
57
+ color: #667eea;
58
+ margin-bottom: 5px;
59
+ }
60
+
61
+ .stat-card .label {
62
+ color: #666;
63
+ font-size: 0.9em;
64
+ text-transform: uppercase;
65
+ letter-spacing: 1px;
66
+ }
67
+
68
+ .no-duplicates {
69
+ background: white;
70
+ padding: 60px;
71
+ border-radius: 15px;
72
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
73
+ text-align: center;
74
+ }
75
+
76
+ .no-duplicates .icon {
77
+ font-size: 5em;
78
+ margin-bottom: 20px;
79
+ }
80
+
81
+ .no-duplicates h2 {
82
+ color: #4CAF50;
83
+ font-size: 2em;
84
+ margin-bottom: 10px;
85
+ }
86
+
87
+ .duplicate-card {
88
+ background: white;
89
+ border-radius: 15px;
90
+ box-shadow: 0 5px 20px rgba(0,0,0,0.1);
91
+ margin-bottom: 25px;
92
+ overflow: hidden;
93
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
94
+ }
95
+
96
+ .duplicate-card:hover {
97
+ transform: translateY(-5px);
98
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
99
+ }
100
+
101
+ .duplicate-header {
102
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
103
+ color: white;
104
+ padding: 20px 30px;
105
+ display: flex;
106
+ justify-content: space-between;
107
+ align-items: center;
108
+ }
109
+
110
+ .duplicate-header h3 {
111
+ font-size: 1.3em;
112
+ }
113
+
114
+ .similarity-badge {
115
+ background: rgba(255,255,255,0.2);
116
+ padding: 8px 20px;
117
+ border-radius: 20px;
118
+ font-weight: bold;
119
+ font-size: 1.2em;
120
+ }
121
+
122
+ .duplicate-body {
123
+ padding: 30px;
124
+ }
125
+
126
+ .function-comparison {
127
+ display: grid;
128
+ grid-template-columns: 1fr 1fr;
129
+ gap: 30px;
130
+ }
131
+
132
+ .function-info {
133
+ background: #f8f9fa;
134
+ padding: 20px;
135
+ border-radius: 10px;
136
+ border-left: 4px solid #667eea;
137
+ }
138
+
139
+ .function-info h4 {
140
+ color: #667eea;
141
+ margin-bottom: 10px;
142
+ font-size: 1.1em;
143
+ }
144
+
145
+ .file-path {
146
+ color: #666;
147
+ font-size: 0.85em;
148
+ margin-bottom: 8px;
149
+ word-break: break-all;
150
+ }
151
+
152
+ .function-name {
153
+ font-family: 'Courier New', monospace;
154
+ background: #e3f2fd;
155
+ padding: 5px 10px;
156
+ border-radius: 5px;
157
+ display: inline-block;
158
+ margin-bottom: 15px;
159
+ color: #1976d2;
160
+ font-weight: bold;
161
+ }
162
+
163
+ .code-preview {
164
+ background: #263238;
165
+ color: #aed581;
166
+ padding: 15px;
167
+ border-radius: 8px;
168
+ font-family: 'Courier New', monospace;
169
+ font-size: 0.9em;
170
+ overflow-x: auto;
171
+ white-space: pre-wrap;
172
+ word-break: break-all;
173
+ max-height: 200px;
174
+ overflow-y: auto;
175
+ }
176
+
177
+ @media (max-width: 768px) {
178
+ .function-comparison {
179
+ grid-template-columns: 1fr;
180
+ }
181
+
182
+ .header h1 {
183
+ font-size: 1.8em;
184
+ }
185
+ }
186
+
187
+ .refresh-btn {
188
+ background: white;
189
+ color: #667eea;
190
+ border: 2px solid #667eea;
191
+ padding: 12px 30px;
192
+ border-radius: 25px;
193
+ font-size: 1em;
194
+ font-weight: bold;
195
+ cursor: pointer;
196
+ transition: all 0.3s ease;
197
+ margin-top: 20px;
198
+ }
199
+
200
+ .refresh-btn:hover {
201
+ background: #667eea;
202
+ color: white;
203
+ transform: scale(1.05);
204
+ }
@@ -0,0 +1,49 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>JavaScript Duplicate Finder</title>
7
+ <link rel="stylesheet" href="ui-styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <div class="header">
12
+ <h1>🔍 JavaScript Duplicate Finder</h1>
13
+ <p>Intelligent code duplication detection</p>
14
+ <button class="refresh-btn" onclick="location.reload()">🔄 Refresh Analysis</button>
15
+ </div>
16
+
17
+ <div class="stats">
18
+ <div class="stat-card">
19
+ <div class="number">{{filesScanned}}</div>
20
+ <div class="label">Files Scanned</div>
21
+ </div>
22
+ <div class="stat-card">
23
+ <div class="number">{{functionsFound}}</div>
24
+ <div class="label">Functions Found</div>
25
+ </div>
26
+ <div class="stat-card">
27
+ <div class="number">{{duplicatesFound}}</div>
28
+ <div class="label">Duplicate Pairs</div>
29
+ </div>
30
+ <div class="stat-card">
31
+ <div class="number">{{threshold}}%</div>
32
+ <div class="label">Threshold</div>
33
+ </div>
34
+ </div>
35
+
36
+ <div id="results">
37
+ <!-- Results will be injected here -->
38
+ </div>
39
+ </div>
40
+
41
+ <script>
42
+ function escapeHtml(text) {
43
+ const div = document.createElement('div');
44
+ div.textContent = text;
45
+ return div.innerHTML;
46
+ }
47
+ </script>
48
+ </body>
49
+ </html>