dslop 1.5.2 → 1.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.6.1
4
+
5
+ [compare changes](https://github.com/turf-sports/dslop/compare/v1.6.0...v1.6.1)
6
+
7
+ ### 🩹 Fixes
8
+
9
+ - Suppress all logs when using --json output ([59e30f1](https://github.com/turf-sports/dslop/commit/59e30f1))
10
+ - Compact JSON output (90K→4K lines) ([3e93895](https://github.com/turf-sports/dslop/commit/3e93895))
11
+
12
+ ### ❤️ Contributors
13
+
14
+ - Siddharth Sharma <sharmasiddharthcs@gmail.com>
15
+
16
+ ## v1.6.0
17
+
18
+ [compare changes](https://github.com/turf-sports/dslop/compare/v1.5.2...v1.6.0)
19
+
20
+ ### 🚀 Enhancements
21
+
22
+ - Smarter defaults - auto-switch to full scan when no changes ([fb918a8](https://github.com/turf-sports/dslop/commit/fb918a8))
23
+
24
+ ### ❤️ Contributors
25
+
26
+ - Siddharth Sharma <sharmasiddharthcs@gmail.com>
27
+
3
28
  ## v1.5.2
4
29
 
5
30
  [compare changes](https://github.com/turf-sports/dslop/compare/v1.5.1...v1.5.2)
package/README.md CHANGED
@@ -6,7 +6,7 @@ Find duplicate code in your codebase.
6
6
  npx dslop
7
7
  ```
8
8
 
9
- By default, checks your branch changes (committed + uncommitted) against the codebase.
9
+ By default, checks your branch changes against the codebase. If no changes found, automatically does a full scan.
10
10
 
11
11
  ## Install
12
12
 
@@ -17,17 +17,18 @@ npm i -g dslop
17
17
  ## Usage
18
18
 
19
19
  ```bash
20
- dslop # check your PR/branch for dupes
21
- dslop --all # scan entire codebase
22
- dslop ./src -m 6 -s 80 # 6 line min, 80% similarity
23
- dslop --all --cross-package # cross-package dupes (monorepos)
20
+ 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)
24
24
  ```
25
25
 
26
26
  ## Options
27
27
 
28
28
  | Flag | Description |
29
29
  |------|-------------|
30
- | `-a, --all` | scan entire codebase (default: uncommitted only) |
30
+ | `-a, --all` | force full codebase scan |
31
+ | `-c, --changes` | force changes-only mode (exit if no changes) |
31
32
  | `-m, --min-lines` | min lines per block (default: 4) |
32
33
  | `-s, --similarity` | similarity threshold 0-100 (default: 70) |
33
34
  | `-e, --extensions` | file extensions (default: ts,tsx,js,jsx) |
@@ -59,20 +60,17 @@ Sliding window over source files extracts overlapping blocks at sizes 4, 6, 9, 1
59
60
 
60
61
  Exact hash matches = exact duplicates. For similar (non-exact) matches, uses character-level similarity.
61
62
 
62
- ### Changed-line filtering (default mode)
63
+ ### Smart defaults
63
64
 
64
- Parses `git diff` output to get exact line ranges of your changes. Only reports duplicates where your changed lines match code elsewhere in the codebase.
65
-
66
- ### Declaration detection (`--all` mode)
67
-
68
- Regex-based extraction of types, interfaces, enums. Compares by name similarity (Levenshtein + word overlap) and content similarity.
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)
69
68
 
70
69
  ## Limitations
71
70
 
72
71
  - **TypeScript/JavaScript only for AST:** AST parsing uses Babel with TS/JSX plugins. Other languages fall back to text-based only.
73
72
  - **No cross-language:** Won't detect a Python function duplicated in TypeScript.
74
73
  - **Comments affect text matching:** Intentional tradeoff. Copy-pasted code often includes comments.
75
- - **Declaration detection is regex:** Can miss edge cases like multi-line generics or decorators.
76
74
  - **Minimum 4 lines:** Shorter duplicates ignored to reduce noise. Use `-m 2` for stricter.
77
75
  - **Memory:** Loads all blocks in memory. Very large codebases (>1M lines) may be slow.
78
76
 
package/dist/index.cjs CHANGED
@@ -51724,7 +51724,7 @@ async function scanDirectory(targetPath, options, enableAST = true) {
51724
51724
  }
51725
51725
 
51726
51726
  // index.ts
51727
- var VERSION = process.env.npm_package_version || "1.5.2";
51727
+ var VERSION = process.env.npm_package_version || "1.6.1";
51728
51728
  function parseDiffOutput(diff, cwd) {
51729
51729
  const changes = /* @__PURE__ */ new Map();
51730
51730
  let currentFile = null;
@@ -51812,13 +51812,15 @@ dslop - Detect Similar/Duplicate Lines Of Programming
51812
51812
  Usage:
51813
51813
  dslop [path] [options]
51814
51814
 
51815
- By default, checks your branch changes (committed + uncommitted) against the codebase.
51815
+ By default, checks your branch changes against the codebase.
51816
+ If no changes are found, automatically scans the entire path.
51816
51817
 
51817
51818
  Arguments:
51818
51819
  path Directory to scan (default: current directory)
51819
51820
 
51820
51821
  Options:
51821
- -a, --all Scan entire codebase (not just uncommitted changes)
51822
+ -a, --all Force full codebase scan
51823
+ -c, --changes Force changes-only mode (exit if no changes found)
51822
51824
  -m, --min-lines <n> Minimum block size in lines (default: ${DEFAULT_MIN_LINES})
51823
51825
  -s, --similarity <n> Minimum similarity threshold 0-100 (default: ${Math.round(DEFAULT_SIMILARITY * 100)})
51824
51826
  -e, --extensions <s> File extensions to scan (default: ${DEFAULT_EXTENSIONS.join(",")})
@@ -51830,10 +51832,10 @@ Options:
51830
51832
  -v, --version Show version
51831
51833
 
51832
51834
  Examples:
51833
- dslop Check your PR/branch changes for duplicates
51834
- dslop --all Scan entire codebase
51835
- dslop ./src -m 6 -s 80 Scan src with 6 line min, 80% similarity
51836
- dslop --all --cross-package Cross-package duplicates in entire codebase
51835
+ dslop Check your PR changes (or full scan if none)
51836
+ dslop ./apps/web Scan apps/web (full scan if no changes there)
51837
+ dslop -c Check PR changes only, exit if none
51838
+ dslop --cross-package Cross-package duplicates
51837
51839
  `);
51838
51840
  }
51839
51841
  function showVersion() {
@@ -51850,6 +51852,7 @@ async function main() {
51850
51852
  normalize: { type: "boolean", default: true },
51851
51853
  "no-normalize": { type: "boolean", default: false },
51852
51854
  all: { type: "boolean", short: "a", default: false },
51855
+ changes: { type: "boolean", short: "c", default: false },
51853
51856
  "cross-package": { type: "boolean", default: false },
51854
51857
  json: { type: "boolean", default: false },
51855
51858
  help: { type: "boolean", short: "h", default: false },
@@ -51871,9 +51874,11 @@ async function main() {
51871
51874
  const extensions = values.extensions.split(",").map((e) => e.trim());
51872
51875
  const ignorePatterns = values.ignore.split(",").map((p) => p.trim());
51873
51876
  const normalize2 = !values["no-normalize"];
51874
- const scanAll = values.all;
51877
+ const forceAll = values.all;
51878
+ const forceChanges = values.changes;
51875
51879
  const crossPackage = values["cross-package"];
51876
51880
  const jsonOutput = values.json;
51881
+ const hasExplicitPath = positionals.length > 0;
51877
51882
  if (minLines < 2) {
51878
51883
  console.error("Error: --min-lines must be at least 2");
51879
51884
  process.exit(1);
@@ -51888,32 +51893,47 @@ async function main() {
51888
51893
  minLines,
51889
51894
  normalize: normalize2
51890
51895
  };
51891
- const changedLines = !scanAll ? getChangedLines(targetPath) : null;
51892
- if (!scanAll && changedLines?.size === 0) {
51893
- console.log("\nNo changes found. Use --all to scan entire codebase.");
51894
- process.exit(0);
51896
+ let scanAll = forceAll;
51897
+ let changedLines = null;
51898
+ if (!forceAll) {
51899
+ changedLines = getChangedLines(targetPath);
51900
+ if (changedLines.size === 0) {
51901
+ if (forceChanges) {
51902
+ console.log("\nNo changes found on current branch.");
51903
+ process.exit(0);
51904
+ }
51905
+ scanAll = true;
51906
+ if (!jsonOutput) {
51907
+ console.log(`
51908
+ No changes detected${hasExplicitPath ? ` in ${targetPath}` : ""}, defaulting to full scan...`);
51909
+ }
51910
+ }
51895
51911
  }
51896
- console.log(`
51912
+ if (!jsonOutput) {
51913
+ console.log(`
51897
51914
  Scanning ${targetPath}...`);
51898
- if (!scanAll && changedLines) {
51899
- console.log(` Mode: checking changed lines in ${changedLines.size} files`);
51900
- } else {
51901
- console.log(` Mode: full codebase scan`);
51902
- }
51903
- console.log(` Extensions: ${extensions.join(", ")}`);
51904
- console.log(` Min block size: ${minLines} lines`);
51905
- console.log(` Similarity threshold: ${Math.round(similarity * 100)}%`);
51906
- if (crossPackage) {
51907
- console.log(` Cross-package: enabled`);
51915
+ if (!scanAll && changedLines && changedLines.size > 0) {
51916
+ console.log(` Mode: checking changed lines in ${changedLines.size} files`);
51917
+ } else {
51918
+ console.log(` Mode: full codebase scan`);
51919
+ }
51920
+ console.log(` Extensions: ${extensions.join(", ")}`);
51921
+ console.log(` Min block size: ${minLines} lines`);
51922
+ console.log(` Similarity threshold: ${Math.round(similarity * 100)}%`);
51923
+ if (crossPackage) {
51924
+ console.log(` Cross-package: enabled`);
51925
+ }
51926
+ console.log();
51908
51927
  }
51909
- console.log();
51910
51928
  try {
51911
51929
  const startTime = performance.now();
51912
51930
  const { blocks, declarations, astBlocks, fileCount, totalLines } = await scanDirectory(targetPath, scanOptions, true);
51913
51931
  const scanTime = performance.now() - startTime;
51914
- console.log(`Scanned ${fileCount} files (${totalLines.toLocaleString()} lines) in ${Math.round(scanTime)}ms`);
51915
- console.log(`Extracted ${blocks.length.toLocaleString()} blocks, ${astBlocks.length.toLocaleString()} functions/classes
51932
+ if (!jsonOutput) {
51933
+ console.log(`Scanned ${fileCount} files (${totalLines.toLocaleString()} lines) in ${Math.round(scanTime)}ms`);
51934
+ console.log(`Extracted ${blocks.length.toLocaleString()} blocks, ${astBlocks.length.toLocaleString()} functions/classes
51916
51935
  `);
51936
+ }
51917
51937
  if (blocks.length === 0 && astBlocks.length === 0) {
51918
51938
  console.log("No code found to analyze.");
51919
51939
  process.exit(0);
@@ -51974,15 +51994,19 @@ Scanning ${targetPath}...`);
51974
51994
  declDuplicates = filterCrossPackage(declDuplicates);
51975
51995
  }
51976
51996
  const totalGroups = duplicates.length + astDuplicates.length + declDuplicates.length;
51977
- console.log(`Found ${totalGroups} duplicate groups in ${Math.round(detectTime)}ms`);
51978
- if (astDuplicates.length > 0 || declDuplicates.length > 0) {
51979
- console.log(` (${duplicates.length} blocks, ${astDuplicates.length} AST, ${declDuplicates.length} declarations)
51997
+ if (!jsonOutput) {
51998
+ console.log(`Found ${totalGroups} duplicate groups in ${Math.round(detectTime)}ms`);
51999
+ if (astDuplicates.length > 0 || declDuplicates.length > 0) {
52000
+ console.log(` (${duplicates.length} blocks, ${astDuplicates.length} AST, ${declDuplicates.length} declarations)
51980
52001
  `);
51981
- } else {
51982
- console.log();
52002
+ } else {
52003
+ console.log();
52004
+ }
51983
52005
  }
51984
52006
  if (totalGroups === 0) {
51985
- if (!scanAll) {
52007
+ if (jsonOutput) {
52008
+ console.log(JSON.stringify({ duplicates: [], ast: [], declarations: [] }, null, 2));
52009
+ } else if (!scanAll) {
51986
52010
  console.log("No duplicates in your changes. You're good!");
51987
52011
  } else if (crossPackage) {
51988
52012
  console.log("No cross-package duplicates found!");
@@ -51992,7 +52016,33 @@ Scanning ${targetPath}...`);
51992
52016
  process.exit(0);
51993
52017
  }
51994
52018
  if (jsonOutput) {
51995
- console.log(JSON.stringify({ duplicates, ast: astDuplicates, declarations: declDuplicates }, null, 2));
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
+ const compactAST = astDuplicates.slice(0, 50).map((a) => ({
52027
+ type: a.type,
52028
+ name: a.matches[0]?.name || "unknown",
52029
+ occurrences: a.matches.length,
52030
+ locations: a.matches.map((m) => ({
52031
+ name: m.name,
52032
+ file: m.filePath.replace(process.cwd() + "/", ""),
52033
+ line: m.startLine,
52034
+ exported: m.exported
52035
+ }))
52036
+ }));
52037
+ console.log(JSON.stringify({
52038
+ summary: {
52039
+ duplicateGroups: duplicates.length,
52040
+ astGroups: astDuplicates.length,
52041
+ declarationGroups: declDuplicates.length
52042
+ },
52043
+ duplicates: compactDuplicates,
52044
+ ast: compactAST
52045
+ }, null, 2));
51996
52046
  } else {
51997
52047
  if (astDuplicates.length > 0) {
51998
52048
  console.log(formatASTDuplicates(astDuplicates, targetPath));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dslop",
3
- "version": "1.5.2",
3
+ "version": "1.6.1",
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",