dslop 1.6.1 → 1.7.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 +12 -0
- package/README.md +117 -36
- package/dist/index.cjs +22 -206
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.7.0
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/turf-sports/dslop/compare/v1.6.1...v1.7.0)
|
|
6
|
+
|
|
7
|
+
### 🚀 Enhancements
|
|
8
|
+
|
|
9
|
+
- AST-only output, filter same-file dupes, sort by occurrences ([53acc46](https://github.com/turf-sports/dslop/commit/53acc46))
|
|
10
|
+
|
|
11
|
+
### ❤️ Contributors
|
|
12
|
+
|
|
13
|
+
- Siddharth Sharma <sharmasiddharthcs@gmail.com>
|
|
14
|
+
|
|
3
15
|
## v1.6.1
|
|
4
16
|
|
|
5
17
|
[compare changes](https://github.com/turf-sports/dslop/compare/v1.6.0...v1.6.1)
|
package/README.md
CHANGED
|
@@ -1,12 +1,92 @@
|
|
|
1
1
|
# dslop
|
|
2
2
|
|
|
3
|
-
Find duplicate code in your codebase.
|
|
3
|
+
Find duplicate functions, types, and code in your codebase using AST analysis.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx dslop
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## What it finds
|
|
10
|
+
|
|
11
|
+
**Real duplicates, not noise.** dslop uses AST parsing to find semantically identical code - functions with the same logic but different variable names, types defined in multiple places, copy-pasted utilities.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
$ dslop --all --json
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
"summary": { "duplicateGroups": 101 },
|
|
18
|
+
"duplicates": [
|
|
19
|
+
{
|
|
20
|
+
"type": "function",
|
|
21
|
+
"name": "loadEnv",
|
|
22
|
+
"occurrences": 6,
|
|
23
|
+
"locations": [
|
|
24
|
+
{ "name": "loadEnv", "file": "scripts/migrate.ts", "line": 8 },
|
|
25
|
+
{ "name": "loadEnv", "file": "scripts/seed.ts", "line": 12 },
|
|
26
|
+
{ "name": "loadEnv", "file": "lib/db/migrate.ts", "line": 7 },
|
|
27
|
+
{ "name": "loadEnv", "file": "lib/db/check-state.ts", "line": 9 }
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "interface",
|
|
32
|
+
"name": "Params",
|
|
33
|
+
"occurrences": 6,
|
|
34
|
+
"locations": [
|
|
35
|
+
{ "name": "Params", "file": "app/api/games/[id]/route.ts", "line": 5 },
|
|
36
|
+
{ "name": "Params", "file": "app/api/games/[id]/state/route.ts", "line": 8 },
|
|
37
|
+
{ "name": "Params", "file": "app/api/games/[id]/details/route.ts", "line": 12 }
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "type",
|
|
42
|
+
"name": "LeaderboardPlayer",
|
|
43
|
+
"occurrences": 3,
|
|
44
|
+
"locations": [
|
|
45
|
+
{ "name": "LeaderboardPlayer", "file": "packages/types/leaderboard.ts", "line": 45, "exported": true },
|
|
46
|
+
{ "name": "LeaderboardPlayer", "file": "apps/mobile/types.ts", "line": 52, "exported": false },
|
|
47
|
+
{ "name": "LeaderboardPlayer", "file": "apps/web/lib/types.ts", "line": 30, "exported": false }
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Monorepo cross-package duplicates
|
|
55
|
+
|
|
56
|
+
Find types and functions duplicated across packages:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
$ dslop --all --cross-package
|
|
60
|
+
|
|
61
|
+
Found 48 duplicate functions/types
|
|
62
|
+
|
|
63
|
+
# Types duplicated between packages/types and apps/
|
|
64
|
+
PrizeDistribution packages/types/game.ts ↔ packages/db/schema/game.ts
|
|
65
|
+
DevicePlatform apps/web/lib/notifications.ts ↔ apps/listener/lib/notifications.ts
|
|
66
|
+
TeamColors apps/mobile/store/types.ts ↔ apps/web/lib/types/colors.ts
|
|
67
|
+
|
|
68
|
+
# Functions copy-pasted between apps/
|
|
69
|
+
subscribeToChannel apps/web/lib/subscriptions.ts ↔ apps/listener/lib/subscriptions.ts
|
|
70
|
+
getTeamLogoUrl packages/shared/logos.ts → also in apps/web (3 copies)
|
|
71
|
+
normalizeError apps/web/sentry.config.ts ↔ apps/listener/lib/sentry.ts
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### PR review mode
|
|
75
|
+
|
|
76
|
+
By default, dslop checks your branch changes against the existing codebase:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
$ dslop
|
|
80
|
+
|
|
81
|
+
Scanning...
|
|
82
|
+
Mode: checking changed lines in 3 files
|
|
83
|
+
|
|
84
|
+
Found 2 duplicate functions/types
|
|
85
|
+
|
|
86
|
+
# You're adding code that already exists elsewhere:
|
|
87
|
+
getUserDisplayName your change: app/profile/page.tsx:19
|
|
88
|
+
exists in: components/sidebar.tsx:50
|
|
89
|
+
```
|
|
10
90
|
|
|
11
91
|
## Install
|
|
12
92
|
|
|
@@ -18,9 +98,10 @@ npm i -g dslop
|
|
|
18
98
|
|
|
19
99
|
```bash
|
|
20
100
|
dslop # check PR changes (or full scan if none)
|
|
21
|
-
dslop
|
|
22
|
-
dslop
|
|
23
|
-
dslop --cross-package # cross-package dupes (monorepos)
|
|
101
|
+
dslop --all # full codebase scan
|
|
102
|
+
dslop --all --json # JSON output for tooling
|
|
103
|
+
dslop --cross-package # only cross-package dupes (monorepos)
|
|
104
|
+
dslop ./apps/web # scan specific directory
|
|
24
105
|
```
|
|
25
106
|
|
|
26
107
|
## Options
|
|
@@ -32,47 +113,47 @@ dslop --cross-package # cross-package dupes (monorepos)
|
|
|
32
113
|
| `-m, --min-lines` | min lines per block (default: 4) |
|
|
33
114
|
| `-s, --similarity` | similarity threshold 0-100 (default: 70) |
|
|
34
115
|
| `-e, --extensions` | file extensions (default: ts,tsx,js,jsx) |
|
|
35
|
-
| `--cross-package` | only show dupes across packages |
|
|
36
|
-
| `--json` |
|
|
116
|
+
| `--cross-package` | only show dupes across packages/apps |
|
|
117
|
+
| `--json` | JSON output |
|
|
37
118
|
|
|
38
119
|
## How it works
|
|
39
120
|
|
|
40
|
-
dslop
|
|
41
|
-
|
|
42
|
-
### 1. AST-based detection (functions/classes)
|
|
121
|
+
dslop parses TypeScript/JavaScript with Babel and extracts functions, classes, types, and interfaces. It normalizes the AST by replacing all identifiers with generic placeholders (`$0`, `$1`, etc.), preserving only the code structure.
|
|
43
122
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
**This catches:**
|
|
123
|
+
This catches:
|
|
47
124
|
- Functions with identical logic but different variable names
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
Example:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
125
|
+
- Types/interfaces defined in multiple places
|
|
126
|
+
- Copy-pasted utilities across packages
|
|
127
|
+
|
|
128
|
+
Example: these two functions are detected as duplicates:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// apps/web/utils.ts
|
|
132
|
+
function getUserInitials(user: User): string {
|
|
133
|
+
const first = user.firstName?.[0] ?? '';
|
|
134
|
+
const last = user.lastName?.[0] ?? '';
|
|
135
|
+
return (first + last).toUpperCase();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// apps/admin/helpers.ts
|
|
139
|
+
function getInitials(person: Person): string {
|
|
140
|
+
const f = person.firstName?.[0] ?? '';
|
|
141
|
+
const l = person.lastName?.[0] ?? '';
|
|
142
|
+
return (f + l).toUpperCase();
|
|
143
|
+
}
|
|
144
|
+
```
|
|
62
145
|
|
|
63
|
-
###
|
|
146
|
+
### What it ignores
|
|
64
147
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
148
|
+
- Same-file duplicates (patterns within a single file)
|
|
149
|
+
- Tiny functions (configurable via `--min-lines`)
|
|
150
|
+
- Common patterns from UI libraries (shadcn components, etc.)
|
|
68
151
|
|
|
69
152
|
## Limitations
|
|
70
153
|
|
|
71
|
-
- **TypeScript/JavaScript only
|
|
72
|
-
- **No cross-language:** Won't detect
|
|
73
|
-
- **
|
|
74
|
-
- **Minimum 4 lines:** Shorter duplicates ignored to reduce noise. Use `-m 2` for stricter.
|
|
75
|
-
- **Memory:** Loads all blocks in memory. Very large codebases (>1M lines) may be slow.
|
|
154
|
+
- **TypeScript/JavaScript only:** AST parsing uses Babel with TS/JSX plugins
|
|
155
|
+
- **No cross-language:** Won't detect duplication across languages
|
|
156
|
+
- **Memory:** Loads all AST nodes in memory. Very large codebases (>1M lines) may be slow
|
|
76
157
|
|
|
77
158
|
## License
|
|
78
159
|
|
package/dist/index.cjs
CHANGED
|
@@ -43616,9 +43616,6 @@ var TEMPLATE_PLACEHOLDER = "<TEMPLATE>";
|
|
|
43616
43616
|
var NUMBER_PLACEHOLDER = "<NUMBER>";
|
|
43617
43617
|
var COLOR_PLACEHOLDER = "<COLOR>";
|
|
43618
43618
|
var MAX_PATH_DISPLAY_LENGTH = 60;
|
|
43619
|
-
var CODE_PREVIEW_CONTEXT_LINES = 3;
|
|
43620
|
-
var MAX_MATCHES_IN_SUMMARY = 5;
|
|
43621
|
-
var MAX_GROUPS_DETAILED = 20;
|
|
43622
43619
|
var SECTION_SEPARATOR = "\u2500".repeat(80);
|
|
43623
43620
|
var COLORS = {
|
|
43624
43621
|
reset: "\x1B[0m",
|
|
@@ -44066,12 +44063,17 @@ function findDuplicates(blocks, minSimilarity, basePath) {
|
|
|
44066
44063
|
const filteredSimilar = similarDuplicates.filter((g) => g.similarity >= minSimilarity);
|
|
44067
44064
|
const allDuplicates = [...exactDuplicates, ...filteredSimilar];
|
|
44068
44065
|
allDuplicates.sort((a, b) => {
|
|
44069
|
-
|
|
44070
|
-
|
|
44071
|
-
|
|
44066
|
+
if (b.occurrences !== a.occurrences) {
|
|
44067
|
+
return b.occurrences - a.occurrences;
|
|
44068
|
+
}
|
|
44069
|
+
return b.lineCount - a.lineCount;
|
|
44072
44070
|
});
|
|
44073
44071
|
const dedupedGroups = deduplicateGroups(allDuplicates);
|
|
44074
|
-
|
|
44072
|
+
const crossFileGroups = dedupedGroups.filter((group) => {
|
|
44073
|
+
const uniqueFiles = new Set(group.matches.map((m) => m.filePath));
|
|
44074
|
+
return uniqueFiles.size > 1;
|
|
44075
|
+
});
|
|
44076
|
+
return crossFileGroups.map((group) => ({
|
|
44075
44077
|
...group,
|
|
44076
44078
|
suggestion: generateRefactoringSuggestion(group, basePath)
|
|
44077
44079
|
}));
|
|
@@ -44713,7 +44715,11 @@ function findASTDuplicates(blocks, _minSimilarity) {
|
|
|
44713
44715
|
});
|
|
44714
44716
|
}
|
|
44715
44717
|
groups.sort((a, b) => b.matches.length - a.matches.length);
|
|
44716
|
-
|
|
44718
|
+
const crossFileGroups = groups.filter((group) => {
|
|
44719
|
+
const uniqueFiles = new Set(group.matches.map((m) => m.filePath));
|
|
44720
|
+
return uniqueFiles.size > 1;
|
|
44721
|
+
});
|
|
44722
|
+
return crossFileGroups;
|
|
44717
44723
|
}
|
|
44718
44724
|
|
|
44719
44725
|
// src/formatter.ts
|
|
@@ -44736,175 +44742,6 @@ function truncatePath(filePath, basePath) {
|
|
|
44736
44742
|
}
|
|
44737
44743
|
return relativePath;
|
|
44738
44744
|
}
|
|
44739
|
-
function getSimilarityBadge(similarity) {
|
|
44740
|
-
const percent = Math.round(similarity * 100);
|
|
44741
|
-
if (similarity === 1) {
|
|
44742
|
-
return `${red}${bold}EXACT${reset}`;
|
|
44743
|
-
} else if (similarity >= 0.9) {
|
|
44744
|
-
return `${yellow}${percent}%${reset}`;
|
|
44745
|
-
} else {
|
|
44746
|
-
return `${cyan}${percent}%${reset}`;
|
|
44747
|
-
}
|
|
44748
|
-
}
|
|
44749
|
-
function formatGroup(group, index, basePath) {
|
|
44750
|
-
const lines = [];
|
|
44751
|
-
const badge = getSimilarityBadge(group.similarity);
|
|
44752
|
-
const impact = group.occurrences * group.lineCount;
|
|
44753
|
-
lines.push(`${bold}Group ${index + 1}${reset} \u2502 ${badge} \u2502 ${group.lineCount} lines \xD7 ${group.occurrences} occurrences = ${green}${impact} lines${reset} of duplication`);
|
|
44754
|
-
lines.push("");
|
|
44755
|
-
const matchesToShow = group.matches.slice(0, MAX_MATCHES_IN_SUMMARY);
|
|
44756
|
-
const hasMore = group.matches.length > MAX_MATCHES_IN_SUMMARY;
|
|
44757
|
-
for (const match2 of matchesToShow) {
|
|
44758
|
-
const displayPath = truncatePath(match2.filePath, basePath);
|
|
44759
|
-
lines.push(` ${dim}\u251C\u2500${reset} ${displayPath}:${yellow}${match2.startLine}${reset}-${yellow}${match2.endLine}${reset}`);
|
|
44760
|
-
}
|
|
44761
|
-
if (hasMore) {
|
|
44762
|
-
lines.push(` ${dim}\u2514\u2500${reset} ${gray}... and ${group.matches.length - MAX_MATCHES_IN_SUMMARY} more${reset}`);
|
|
44763
|
-
}
|
|
44764
|
-
const firstMatch = group.matches[0];
|
|
44765
|
-
if (firstMatch) {
|
|
44766
|
-
lines.push("");
|
|
44767
|
-
lines.push(` ${dim}Code preview:${reset}`);
|
|
44768
|
-
const previewLines = firstMatch.content.split("\n").slice(0, CODE_PREVIEW_CONTEXT_LINES).map((line) => ` ${gray}\u2502${reset} ${dim}${line.slice(0, 80)}${line.length > 80 ? "..." : ""}${reset}`);
|
|
44769
|
-
lines.push(...previewLines);
|
|
44770
|
-
if (firstMatch.content.split("\n").length > CODE_PREVIEW_CONTEXT_LINES) {
|
|
44771
|
-
lines.push(` ${gray}\u2502${reset} ${dim}...${reset}`);
|
|
44772
|
-
}
|
|
44773
|
-
}
|
|
44774
|
-
if (group.suggestion) {
|
|
44775
|
-
lines.push("");
|
|
44776
|
-
const confidenceColor = group.suggestion.confidence === "high" ? green : group.suggestion.confidence === "medium" ? yellow : gray;
|
|
44777
|
-
lines.push(` ${magenta}\u2192 Suggestion:${reset} Move to ${cyan}${group.suggestion.targetLocation}${reset}`);
|
|
44778
|
-
if (group.suggestion.suggestedName) {
|
|
44779
|
-
lines.push(` ${dim} Name: ${group.suggestion.suggestedName}${reset}`);
|
|
44780
|
-
}
|
|
44781
|
-
lines.push(` ${dim} ${group.suggestion.reason} ${confidenceColor}[${group.suggestion.confidence}]${reset}`);
|
|
44782
|
-
}
|
|
44783
|
-
return lines.join("\n");
|
|
44784
|
-
}
|
|
44785
|
-
function formatOutput(groups, basePath) {
|
|
44786
|
-
if (groups.length === 0) {
|
|
44787
|
-
return `${green}No duplicates found!${reset}`;
|
|
44788
|
-
}
|
|
44789
|
-
const lines = [];
|
|
44790
|
-
lines.push(SECTION_SEPARATOR);
|
|
44791
|
-
lines.push(`${bold}DUPLICATE CODE DETECTED${reset}`);
|
|
44792
|
-
lines.push(SECTION_SEPARATOR);
|
|
44793
|
-
lines.push("");
|
|
44794
|
-
const groupsToShow = groups.slice(0, MAX_GROUPS_DETAILED);
|
|
44795
|
-
for (let i = 0; i < groupsToShow.length; i++) {
|
|
44796
|
-
const group = groupsToShow[i];
|
|
44797
|
-
if (group) {
|
|
44798
|
-
lines.push(formatGroup(group, i, basePath));
|
|
44799
|
-
lines.push("");
|
|
44800
|
-
}
|
|
44801
|
-
}
|
|
44802
|
-
if (groups.length > MAX_GROUPS_DETAILED) {
|
|
44803
|
-
lines.push(`${dim}... and ${groups.length - MAX_GROUPS_DETAILED} more groups${reset}`);
|
|
44804
|
-
lines.push("");
|
|
44805
|
-
}
|
|
44806
|
-
lines.push(SECTION_SEPARATOR);
|
|
44807
|
-
return lines.join("\n");
|
|
44808
|
-
}
|
|
44809
|
-
function formatStats(groups) {
|
|
44810
|
-
if (groups.length === 0) {
|
|
44811
|
-
return "";
|
|
44812
|
-
}
|
|
44813
|
-
const totalDuplicateLines = groups.reduce(
|
|
44814
|
-
(sum, g) => sum + g.lineCount * g.occurrences,
|
|
44815
|
-
0
|
|
44816
|
-
);
|
|
44817
|
-
const exactMatches = groups.filter((g) => g.similarity === 1).length;
|
|
44818
|
-
const similarMatches = groups.length - exactMatches;
|
|
44819
|
-
const avgSimilarity = groups.reduce((sum, g) => sum + g.similarity, 0) / groups.length;
|
|
44820
|
-
const uniqueFiles = /* @__PURE__ */ new Set();
|
|
44821
|
-
for (const group of groups) {
|
|
44822
|
-
for (const match2 of group.matches) {
|
|
44823
|
-
uniqueFiles.add(match2.filePath);
|
|
44824
|
-
}
|
|
44825
|
-
}
|
|
44826
|
-
const lines = [];
|
|
44827
|
-
lines.push(`${bold}SUMMARY${reset}`);
|
|
44828
|
-
lines.push(SECTION_SEPARATOR);
|
|
44829
|
-
lines.push(` Total duplicate groups: ${bold}${groups.length}${reset}`);
|
|
44830
|
-
lines.push(` Exact matches: ${bold}${exactMatches}${reset}`);
|
|
44831
|
-
lines.push(` Similar matches: ${bold}${similarMatches}${reset}`);
|
|
44832
|
-
lines.push(` Files affected: ${bold}${uniqueFiles.size}${reset}`);
|
|
44833
|
-
lines.push(` Total duplicate lines: ${red}${bold}${totalDuplicateLines.toLocaleString()}${reset}`);
|
|
44834
|
-
lines.push(` Average similarity: ${bold}${Math.round(avgSimilarity * 100)}%${reset}`);
|
|
44835
|
-
lines.push(SECTION_SEPARATOR);
|
|
44836
|
-
lines.push("");
|
|
44837
|
-
lines.push(`${dim}Tip: Use --json for machine-readable output${reset}`);
|
|
44838
|
-
return lines.join("\n");
|
|
44839
|
-
}
|
|
44840
|
-
var TYPE_LABELS = {
|
|
44841
|
-
type: "Type",
|
|
44842
|
-
interface: "Interface",
|
|
44843
|
-
function: "Function",
|
|
44844
|
-
class: "Class",
|
|
44845
|
-
const: "Constant",
|
|
44846
|
-
enum: "Enum"
|
|
44847
|
-
};
|
|
44848
|
-
function formatDeclarationGroup(group, index, basePath) {
|
|
44849
|
-
const lines = [];
|
|
44850
|
-
const typeLabel = TYPE_LABELS[group.type] || group.type;
|
|
44851
|
-
const simBadge = group.similarity >= 0.95 ? `${red}${bold}EXACT${reset}` : `${yellow}${Math.round(group.similarity * 100)}%${reset}`;
|
|
44852
|
-
lines.push(`${bold}${typeLabel} ${index + 1}${reset} \u2502 ${simBadge} \u2502 ${group.matches.length} occurrences`);
|
|
44853
|
-
if (group.nameSimilarity > 0 && group.nameSimilarity < 1) {
|
|
44854
|
-
lines.push(` ${dim}Name similarity: ${Math.round(group.nameSimilarity * 100)}%${reset}`);
|
|
44855
|
-
}
|
|
44856
|
-
lines.push("");
|
|
44857
|
-
for (const match2 of group.matches.slice(0, 5)) {
|
|
44858
|
-
const displayPath = truncatePath(match2.filePath, basePath);
|
|
44859
|
-
const exportBadge = match2.exported ? `${green}exported${reset}` : `${gray}local${reset}`;
|
|
44860
|
-
lines.push(` ${dim}\u251C\u2500${reset} ${cyan}${match2.name}${reset} [${exportBadge}]`);
|
|
44861
|
-
lines.push(` ${displayPath}:${yellow}${match2.startLine}${reset}-${yellow}${match2.endLine}${reset}`);
|
|
44862
|
-
}
|
|
44863
|
-
if (group.matches.length > 5) {
|
|
44864
|
-
lines.push(` ${dim}\u2514\u2500${reset} ${gray}... and ${group.matches.length - 5} more${reset}`);
|
|
44865
|
-
}
|
|
44866
|
-
lines.push("");
|
|
44867
|
-
lines.push(` ${magenta}\u2192${reset} ${group.suggestion}`);
|
|
44868
|
-
return lines.join("\n");
|
|
44869
|
-
}
|
|
44870
|
-
function formatDeclarations(groups, basePath) {
|
|
44871
|
-
if (groups.length === 0) {
|
|
44872
|
-
return "";
|
|
44873
|
-
}
|
|
44874
|
-
const lines = [];
|
|
44875
|
-
lines.push("");
|
|
44876
|
-
lines.push(SECTION_SEPARATOR);
|
|
44877
|
-
lines.push(`${bold}DUPLICATE DECLARATIONS${reset}`);
|
|
44878
|
-
lines.push(SECTION_SEPARATOR);
|
|
44879
|
-
lines.push("");
|
|
44880
|
-
const byType = /* @__PURE__ */ new Map();
|
|
44881
|
-
for (const group of groups) {
|
|
44882
|
-
const existing = byType.get(group.type) || [];
|
|
44883
|
-
existing.push(group);
|
|
44884
|
-
byType.set(group.type, existing);
|
|
44885
|
-
}
|
|
44886
|
-
let globalIndex = 0;
|
|
44887
|
-
for (const [type, typeGroups] of byType) {
|
|
44888
|
-
const typeLabel = TYPE_LABELS[type] || type;
|
|
44889
|
-
lines.push(`${bold}${typeLabel}s (${typeGroups.length})${reset}`);
|
|
44890
|
-
lines.push("");
|
|
44891
|
-
for (const group of typeGroups.slice(0, 10)) {
|
|
44892
|
-
lines.push(formatDeclarationGroup(group, globalIndex++, basePath));
|
|
44893
|
-
lines.push("");
|
|
44894
|
-
}
|
|
44895
|
-
if (typeGroups.length > 10) {
|
|
44896
|
-
lines.push(`${dim}... and ${typeGroups.length - 10} more ${typeLabel.toLowerCase()}s${reset}`);
|
|
44897
|
-
lines.push("");
|
|
44898
|
-
}
|
|
44899
|
-
}
|
|
44900
|
-
lines.push(SECTION_SEPARATOR);
|
|
44901
|
-
const totalDups = groups.reduce((sum, g) => sum + g.matches.length, 0);
|
|
44902
|
-
lines.push(`${bold}Declaration Summary${reset}`);
|
|
44903
|
-
lines.push(` Duplicate groups: ${bold}${groups.length}${reset}`);
|
|
44904
|
-
lines.push(` Total occurrences: ${bold}${totalDups}${reset}`);
|
|
44905
|
-
lines.push(SECTION_SEPARATOR);
|
|
44906
|
-
return lines.join("\n");
|
|
44907
|
-
}
|
|
44908
44745
|
var AST_TYPE_LABELS = {
|
|
44909
44746
|
function: "Function",
|
|
44910
44747
|
arrow: "Arrow Function",
|
|
@@ -51724,7 +51561,7 @@ async function scanDirectory(targetPath, options, enableAST = true) {
|
|
|
51724
51561
|
}
|
|
51725
51562
|
|
|
51726
51563
|
// index.ts
|
|
51727
|
-
var VERSION = process.env.npm_package_version || "1.
|
|
51564
|
+
var VERSION = process.env.npm_package_version || "1.7.0";
|
|
51728
51565
|
function parseDiffOutput(diff, cwd) {
|
|
51729
51566
|
const changes = /* @__PURE__ */ new Map();
|
|
51730
51567
|
let currentFile = null;
|
|
@@ -51993,19 +51830,13 @@ Scanning ${targetPath}...`);
|
|
|
51993
51830
|
astDuplicates = filterCrossPackage(astDuplicates);
|
|
51994
51831
|
declDuplicates = filterCrossPackage(declDuplicates);
|
|
51995
51832
|
}
|
|
51996
|
-
const totalGroups = duplicates.length + astDuplicates.length + declDuplicates.length;
|
|
51997
51833
|
if (!jsonOutput) {
|
|
51998
|
-
console.log(`Found ${
|
|
51999
|
-
if (astDuplicates.length > 0 || declDuplicates.length > 0) {
|
|
52000
|
-
console.log(` (${duplicates.length} blocks, ${astDuplicates.length} AST, ${declDuplicates.length} declarations)
|
|
51834
|
+
console.log(`Found ${astDuplicates.length} duplicate functions/types in ${Math.round(detectTime)}ms
|
|
52001
51835
|
`);
|
|
52002
|
-
} else {
|
|
52003
|
-
console.log();
|
|
52004
|
-
}
|
|
52005
51836
|
}
|
|
52006
|
-
if (
|
|
51837
|
+
if (astDuplicates.length === 0) {
|
|
52007
51838
|
if (jsonOutput) {
|
|
52008
|
-
console.log(JSON.stringify({
|
|
51839
|
+
console.log(JSON.stringify({ summary: { duplicateGroups: 0 }, duplicates: [] }, null, 2));
|
|
52009
51840
|
} else if (!scanAll) {
|
|
52010
51841
|
console.log("No duplicates in your changes. You're good!");
|
|
52011
51842
|
} else if (crossPackage) {
|
|
@@ -52016,13 +51847,6 @@ Scanning ${targetPath}...`);
|
|
|
52016
51847
|
process.exit(0);
|
|
52017
51848
|
}
|
|
52018
51849
|
if (jsonOutput) {
|
|
52019
|
-
const compactDuplicates = duplicates.slice(0, 100).map((d) => ({
|
|
52020
|
-
lines: d.lineCount,
|
|
52021
|
-
occurrences: d.occurrences,
|
|
52022
|
-
similarity: Math.round(d.similarity * 100),
|
|
52023
|
-
preview: d.matches[0]?.content.split("\n").slice(0, 3).join("\n") || "",
|
|
52024
|
-
locations: d.matches.slice(0, 10).map((m) => `${m.filePath.replace(process.cwd() + "/", "")}:${m.startLine}`)
|
|
52025
|
-
}));
|
|
52026
51850
|
const compactAST = astDuplicates.slice(0, 50).map((a) => ({
|
|
52027
51851
|
type: a.type,
|
|
52028
51852
|
name: a.matches[0]?.name || "unknown",
|
|
@@ -52036,23 +51860,15 @@ Scanning ${targetPath}...`);
|
|
|
52036
51860
|
}));
|
|
52037
51861
|
console.log(JSON.stringify({
|
|
52038
51862
|
summary: {
|
|
52039
|
-
duplicateGroups:
|
|
52040
|
-
astGroups: astDuplicates.length,
|
|
52041
|
-
declarationGroups: declDuplicates.length
|
|
51863
|
+
duplicateGroups: astDuplicates.length
|
|
52042
51864
|
},
|
|
52043
|
-
duplicates:
|
|
52044
|
-
ast: compactAST
|
|
51865
|
+
duplicates: compactAST
|
|
52045
51866
|
}, null, 2));
|
|
52046
51867
|
} else {
|
|
52047
51868
|
if (astDuplicates.length > 0) {
|
|
52048
51869
|
console.log(formatASTDuplicates(astDuplicates, targetPath));
|
|
52049
|
-
}
|
|
52050
|
-
|
|
52051
|
-
console.log(formatOutput(duplicates, targetPath));
|
|
52052
|
-
console.log(formatStats(duplicates));
|
|
52053
|
-
}
|
|
52054
|
-
if (declDuplicates.length > 0) {
|
|
52055
|
-
console.log(formatDeclarations(declDuplicates, targetPath));
|
|
51870
|
+
} else {
|
|
51871
|
+
console.log("No duplicate functions or types found!");
|
|
52056
51872
|
}
|
|
52057
51873
|
}
|
|
52058
51874
|
} catch (error) {
|