dslop 1.6.0 → 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 CHANGED
@@ -1,5 +1,30 @@
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
+
15
+ ## v1.6.1
16
+
17
+ [compare changes](https://github.com/turf-sports/dslop/compare/v1.6.0...v1.6.1)
18
+
19
+ ### 🩹 Fixes
20
+
21
+ - Suppress all logs when using --json output ([59e30f1](https://github.com/turf-sports/dslop/commit/59e30f1))
22
+ - Compact JSON output (90K→4K lines) ([3e93895](https://github.com/turf-sports/dslop/commit/3e93895))
23
+
24
+ ### ❤️ Contributors
25
+
26
+ - Siddharth Sharma <sharmasiddharthcs@gmail.com>
27
+
3
28
  ## v1.6.0
4
29
 
5
30
  [compare changes](https://github.com/turf-sports/dslop/compare/v1.5.2...v1.6.0)
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
- By default, checks your branch changes against the codebase. If no changes found, automatically does a full scan.
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 ./apps/web # scan apps/web (full if no changes there)
22
- dslop -c # changes only, exit if none found
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` | json output |
116
+ | `--cross-package` | only show dupes across packages/apps |
117
+ | `--json` | JSON output |
37
118
 
38
119
  ## How it works
39
120
 
40
- dslop uses two detection methods in parallel:
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
- Parses TypeScript/JavaScript with Babel to extract functions and classes. Normalizes the AST by replacing all identifiers with generic placeholders (`$0`, `$1`, etc.), preserving only the code structure.
45
-
46
- **This catches:**
123
+ This catches:
47
124
  - Functions with identical logic but different variable names
48
- - Renamed copies of existing functions
49
- - Structurally identical classes
50
-
51
- Example: `calculateSum(numbers)` and `computeTotal(items)` with the same loop structure will match.
52
-
53
- ### 2. Text-based detection (code blocks)
54
-
55
- Sliding window over source files extracts overlapping blocks at sizes 4, 6, 9, 13... lines. Before hashing, code is normalized:
56
- - String literals `"<STRING>"`
57
- - Numbers `<NUMBER>`
58
- - Whitespace collapsed
59
- - Comments preserved (intentional - comments often indicate copy-paste)
60
-
61
- Exact hash matches = exact duplicates. For similar (non-exact) matches, uses character-level similarity.
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
- ### Smart defaults
146
+ ### What it ignores
64
147
 
65
- 1. If you have branch changes checks those against the codebase
66
- 2. If no changes found → automatically scans the entire target path
67
- 3. Use `-c` to force changes-only mode (useful in CI)
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 for AST:** AST parsing uses Babel with TS/JSX plugins. Other languages fall back to text-based only.
72
- - **No cross-language:** Won't detect a Python function duplicated in TypeScript.
73
- - **Comments affect text matching:** Intentional tradeoff. Copy-pasted code often includes comments.
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
- const impactA = a.occurrences * a.lineCount;
44070
- const impactB = b.occurrences * b.lineCount;
44071
- return impactB - impactA;
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
- return dedupedGroups.map((group) => ({
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
- return groups;
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.6.0";
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,16 +51830,14 @@ Scanning ${targetPath}...`);
51993
51830
  astDuplicates = filterCrossPackage(astDuplicates);
51994
51831
  declDuplicates = filterCrossPackage(declDuplicates);
51995
51832
  }
51996
- const totalGroups = duplicates.length + astDuplicates.length + declDuplicates.length;
51997
- console.log(`Found ${totalGroups} duplicate groups in ${Math.round(detectTime)}ms`);
51998
- if (astDuplicates.length > 0 || declDuplicates.length > 0) {
51999
- console.log(` (${duplicates.length} blocks, ${astDuplicates.length} AST, ${declDuplicates.length} declarations)
51833
+ if (!jsonOutput) {
51834
+ console.log(`Found ${astDuplicates.length} duplicate functions/types in ${Math.round(detectTime)}ms
52000
51835
  `);
52001
- } else {
52002
- console.log();
52003
51836
  }
52004
- if (totalGroups === 0) {
52005
- if (!scanAll) {
51837
+ if (astDuplicates.length === 0) {
51838
+ if (jsonOutput) {
51839
+ console.log(JSON.stringify({ summary: { duplicateGroups: 0 }, duplicates: [] }, null, 2));
51840
+ } else if (!scanAll) {
52006
51841
  console.log("No duplicates in your changes. You're good!");
52007
51842
  } else if (crossPackage) {
52008
51843
  console.log("No cross-package duplicates found!");
@@ -52012,17 +51847,28 @@ Scanning ${targetPath}...`);
52012
51847
  process.exit(0);
52013
51848
  }
52014
51849
  if (jsonOutput) {
52015
- console.log(JSON.stringify({ duplicates, ast: astDuplicates, declarations: declDuplicates }, null, 2));
51850
+ const compactAST = astDuplicates.slice(0, 50).map((a) => ({
51851
+ type: a.type,
51852
+ name: a.matches[0]?.name || "unknown",
51853
+ occurrences: a.matches.length,
51854
+ locations: a.matches.map((m) => ({
51855
+ name: m.name,
51856
+ file: m.filePath.replace(process.cwd() + "/", ""),
51857
+ line: m.startLine,
51858
+ exported: m.exported
51859
+ }))
51860
+ }));
51861
+ console.log(JSON.stringify({
51862
+ summary: {
51863
+ duplicateGroups: astDuplicates.length
51864
+ },
51865
+ duplicates: compactAST
51866
+ }, null, 2));
52016
51867
  } else {
52017
51868
  if (astDuplicates.length > 0) {
52018
51869
  console.log(formatASTDuplicates(astDuplicates, targetPath));
52019
- }
52020
- if (duplicates.length > 0) {
52021
- console.log(formatOutput(duplicates, targetPath));
52022
- console.log(formatStats(duplicates));
52023
- }
52024
- if (declDuplicates.length > 0) {
52025
- console.log(formatDeclarations(declDuplicates, targetPath));
51870
+ } else {
51871
+ console.log("No duplicate functions or types found!");
52026
51872
  }
52027
51873
  }
52028
51874
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dslop",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Detect Similar/Duplicate Lines Of Programming - Find code duplication in your codebase",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",