dslop 1.5.0 → 1.5.2

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,29 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.5.2
4
+
5
+ [compare changes](https://github.com/turf-sports/dslop/compare/v1.5.1...v1.5.2)
6
+
7
+ ### 📖 Documentation
8
+
9
+ - Update README with AST-based detection details ([2bdb4f4](https://github.com/turf-sports/dslop/commit/2bdb4f4))
10
+
11
+ ### ❤️ Contributors
12
+
13
+ - Siddharth Sharma <sharmasiddharthcs@gmail.com>
14
+
15
+ ## v1.5.1
16
+
17
+ [compare changes](https://github.com/turf-sports/dslop/compare/v1.5.0...v1.5.1)
18
+
19
+ ### 🩹 Fixes
20
+
21
+ - Resolve all lint errors ([0de0b6c](https://github.com/turf-sports/dslop/commit/0de0b6c))
22
+
23
+ ### ❤️ Contributors
24
+
25
+ - Siddharth Sharma <sharmasiddharthcs@gmail.com>
26
+
3
27
  ## v1.5.0
4
28
 
5
29
  [compare changes](https://github.com/turf-sports/dslop/compare/v1.4.1...v1.5.0)
package/README.md CHANGED
@@ -36,26 +36,42 @@ dslop --all --cross-package # cross-package dupes (monorepos)
36
36
 
37
37
  ## How it works
38
38
 
39
- **Block extraction:** Sliding window over source files. Extracts overlapping blocks at sizes 4, 6, 9, 13... lines. For blocks <10 lines, step=1 (every line). Larger blocks use step=blockSize/2.
39
+ dslop uses two detection methods in parallel:
40
40
 
41
- **Normalization:** Before hashing, code is normalized:
41
+ ### 1. AST-based detection (functions/classes)
42
+
43
+ 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.
44
+
45
+ **This catches:**
46
+ - Functions with identical logic but different variable names
47
+ - Renamed copies of existing functions
48
+ - Structurally identical classes
49
+
50
+ Example: `calculateSum(numbers)` and `computeTotal(items)` with the same loop structure will match.
51
+
52
+ ### 2. Text-based detection (code blocks)
53
+
54
+ Sliding window over source files extracts overlapping blocks at sizes 4, 6, 9, 13... lines. Before hashing, code is normalized:
42
55
  - String literals → `"<STRING>"`
43
56
  - Numbers → `<NUMBER>`
44
57
  - Whitespace collapsed
45
58
  - Comments preserved (intentional - comments often indicate copy-paste)
46
59
 
47
- **Matching:** Normalized blocks are hashed. Exact hash matches = exact duplicates. For similar (non-exact) matches, uses character-level similarity on a sample of blocks per hash bucket.
60
+ Exact hash matches = exact duplicates. For similar (non-exact) matches, uses character-level similarity.
61
+
62
+ ### Changed-line filtering (default mode)
63
+
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.
48
65
 
49
- **Declaration detection** (`--all` mode): Regex-based extraction of types, interfaces, functions, classes. Compares by name similarity (Levenshtein + word overlap) and content similarity.
66
+ ### Declaration detection (`--all` mode)
50
67
 
51
- **Changed-line filtering** (default mode): Parses `git diff` output to get exact line ranges. Only reports duplicates where your changed lines match code elsewhere.
68
+ Regex-based extraction of types, interfaces, enums. Compares by name similarity (Levenshtein + word overlap) and content similarity.
52
69
 
53
70
  ## Limitations
54
71
 
55
- - **Text-based, not AST:** Doesn't understand code structure. A reformatted function won't match the original. Two semantically identical functions with different variable names won't match.
56
- - **TypeScript/JavaScript focused:** Default extensions are ts/tsx/js/jsx. Works on any text but tuned for JS-like syntax.
72
+ - **TypeScript/JavaScript only for AST:** AST parsing uses Babel with TS/JSX plugins. Other languages fall back to text-based only.
57
73
  - **No cross-language:** Won't detect a Python function duplicated in TypeScript.
58
- - **Comments affect matching:** Intentional tradeoff. Copy-pasted code often includes comments.
74
+ - **Comments affect text matching:** Intentional tradeoff. Copy-pasted code often includes comments.
59
75
  - **Declaration detection is regex:** Can miss edge cases like multi-line generics or decorators.
60
76
  - **Minimum 4 lines:** Shorter duplicates ignored to reduce noise. Use `-m 2` for stricter.
61
77
  - **Memory:** Loads all blocks in memory. Very large codebases (>1M lines) may be slow.
package/dist/index.cjs CHANGED
@@ -43566,9 +43566,9 @@ var require_lib8 = __commonJS({
43566
43566
  });
43567
43567
 
43568
43568
  // index.ts
43569
- var import_node_util = require("node:util");
43570
43569
  var import_node_child_process = require("node:child_process");
43571
43570
  var import_node_path4 = __toESM(require("node:path"), 1);
43571
+ var import_node_util = require("node:util");
43572
43572
 
43573
43573
  // src/constants.ts
43574
43574
  var DEFAULT_MIN_LINES = 4;
@@ -43751,6 +43751,7 @@ function extractDeclarations(content, filePath) {
43751
43751
  let lineStart = 0;
43752
43752
  for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
43753
43753
  const line = lines[lineIndex];
43754
+ if (!line) continue;
43754
43755
  const trimmed = line.trimStart();
43755
43756
  if (!QUICK_CHECK.test(trimmed)) {
43756
43757
  lineStart += line.length + 1;
@@ -43842,27 +43843,27 @@ function splitCamelCase(str) {
43842
43843
  function levenshteinDistance(a, b) {
43843
43844
  if (a.length === 0) return b.length;
43844
43845
  if (b.length === 0) return a.length;
43845
- const matrix = [];
43846
- for (let i = 0; i <= b.length; i++) {
43847
- matrix[i] = [i];
43848
- }
43849
- for (let j = 0; j <= a.length; j++) {
43850
- matrix[0][j] = j;
43851
- }
43846
+ const matrix = Array.from(
43847
+ { length: b.length + 1 },
43848
+ (_, i) => Array.from({ length: a.length + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
43849
+ );
43852
43850
  for (let i = 1; i <= b.length; i++) {
43853
43851
  for (let j = 1; j <= a.length; j++) {
43852
+ const prevRow = matrix[i - 1];
43853
+ const currRow = matrix[i];
43854
+ if (!prevRow || !currRow) continue;
43854
43855
  if (b[i - 1] === a[j - 1]) {
43855
- matrix[i][j] = matrix[i - 1][j - 1];
43856
+ currRow[j] = prevRow[j - 1] ?? 0;
43856
43857
  } else {
43857
- matrix[i][j] = Math.min(
43858
- matrix[i - 1][j - 1] + 1,
43859
- matrix[i][j - 1] + 1,
43860
- matrix[i - 1][j] + 1
43858
+ currRow[j] = Math.min(
43859
+ (prevRow[j - 1] ?? 0) + 1,
43860
+ (currRow[j - 1] ?? 0) + 1,
43861
+ (prevRow[j] ?? 0) + 1
43861
43862
  );
43862
43863
  }
43863
43864
  }
43864
43865
  }
43865
- return matrix[b.length][a.length];
43866
+ return matrix[b.length]?.[a.length] ?? Math.max(a.length, b.length);
43866
43867
  }
43867
43868
 
43868
43869
  // src/detector.ts
@@ -44099,7 +44100,7 @@ function filterOverlappingBlocks(blocks) {
44099
44100
  return result;
44100
44101
  }
44101
44102
  function extractPackageInfo(filePath) {
44102
- const match2 = filePath.match(/(apps|packages|libs)\/([^\/]+)\/(.+)/);
44103
+ const match2 = filePath.match(/(apps|packages|libs)\/([^/]+)\/(.+)/);
44103
44104
  if (match2) {
44104
44105
  return {
44105
44106
  type: match2[1],
@@ -44114,7 +44115,7 @@ function extractPackageInfo(filePath) {
44114
44115
  subPath: parts.slice(1).join("/")
44115
44116
  };
44116
44117
  }
44117
- function inferSuggestedName(pattern, matches) {
44118
+ function inferSuggestedName(_pattern, matches) {
44118
44119
  const firstMatch = matches[0];
44119
44120
  if (!firstMatch) return void 0;
44120
44121
  const content = firstMatch.content;
@@ -44159,7 +44160,7 @@ function inferSuggestedName(pattern, matches) {
44159
44160
  }
44160
44161
  return void 0;
44161
44162
  }
44162
- function generateRefactoringSuggestion(group, basePath) {
44163
+ function generateRefactoringSuggestion(group, _basePath) {
44163
44164
  const matches = group.matches;
44164
44165
  const packageInfos = matches.map((m) => extractPackageInfo(m.filePath));
44165
44166
  const uniquePackages = /* @__PURE__ */ new Map();
@@ -44171,7 +44172,7 @@ function generateRefactoringSuggestion(group, basePath) {
44171
44172
  }
44172
44173
  const packageList = Array.from(uniquePackages.values());
44173
44174
  const suggestedName = inferSuggestedName(group.pattern, matches);
44174
- if (packageList.length === 1) {
44175
+ if (packageList.length === 1 && packageList[0]) {
44175
44176
  const pkg = packageList[0];
44176
44177
  const subPaths = packageInfos.map((p) => p.subPath);
44177
44178
  const commonPrefix = findCommonPrefix(subPaths);
@@ -44207,6 +44208,7 @@ function generateRefactoringSuggestion(group, basePath) {
44207
44208
  const sharedPackage = packages.find(
44208
44209
  (p) => p.name === "shared" || p.name === "common" || p.name === "utils"
44209
44210
  ) ?? packages[0];
44211
+ if (!sharedPackage) return { targetLocation: "packages/shared/src", reason: "Duplicated across packages", confidence: "low", suggestedName };
44210
44212
  return {
44211
44213
  targetLocation: `packages/${sharedPackage.name}/src`,
44212
44214
  reason: `Already exists in ${sharedPackage.name}, also duplicated in ${apps.map((a) => a.name).join(", ")}`,
@@ -44291,6 +44293,7 @@ function findDeclarationDuplicates(declarations, minSimilarity) {
44291
44293
  for (let i = 0; i < typeDecls.length; i++) {
44292
44294
  if (processed.has(i)) continue;
44293
44295
  const declA = typeDecls[i];
44296
+ if (!declA) continue;
44294
44297
  const matches = [{
44295
44298
  name: declA.name,
44296
44299
  filePath: declA.filePath,
@@ -44304,6 +44307,7 @@ function findDeclarationDuplicates(declarations, minSimilarity) {
44304
44307
  for (let j = i + 1; j < typeDecls.length; j++) {
44305
44308
  if (processed.has(j)) continue;
44306
44309
  const declB = typeDecls[j];
44310
+ if (!declB) continue;
44307
44311
  if (declA.filePath === declB.filePath && Math.abs(declA.startLine - declB.startLine) < 5) continue;
44308
44312
  const nameSim = calculateNameSimilarity(declA.name, declB.name);
44309
44313
  const contentSim = calculateSimilarityFast(declA.normalized, declB.normalized);
@@ -44414,7 +44418,7 @@ function normalizeNode(node, idMap, counter) {
44414
44418
  if (!idMap.has(name)) {
44415
44419
  idMap.set(name, `$${counter.val++}`);
44416
44420
  }
44417
- return idMap.get(name);
44421
+ return idMap.get(name) ?? name;
44418
44422
  }
44419
44423
  if (t.isStringLiteral(node)) return '"S"';
44420
44424
  if (t.isNumericLiteral(node)) return "N";
@@ -44424,7 +44428,7 @@ function normalizeNode(node, idMap, counter) {
44424
44428
  if (t.isFile(node)) return normalizeNode(node.program, idMap, counter);
44425
44429
  if (t.isProgram(node)) return node.body.map((n) => normalizeNode(n, idMap, counter)).join(";");
44426
44430
  if (t.isBlockStatement(node)) {
44427
- return "{" + node.body.map((n) => normalizeNode(n, idMap, counter)).join(";") + "}";
44431
+ return `{${node.body.map((n) => normalizeNode(n, idMap, counter)).join(";")}}`;
44428
44432
  }
44429
44433
  if (t.isFunctionDeclaration(node) || t.isFunctionExpression(node)) {
44430
44434
  const params = node.params.map((p) => normalizeNode(p, idMap, counter)).join(",");
@@ -44439,20 +44443,20 @@ function normalizeNode(node, idMap, counter) {
44439
44443
  return `${async}(${params})=>${body}`;
44440
44444
  }
44441
44445
  if (t.isVariableDeclaration(node)) {
44442
- return node.kind + " " + node.declarations.map((d) => normalizeNode(d, idMap, counter)).join(",");
44446
+ return `${node.kind} ${node.declarations.map((d) => normalizeNode(d, idMap, counter)).join(",")}`;
44443
44447
  }
44444
44448
  if (t.isVariableDeclarator(node)) {
44445
44449
  const id = normalizeNode(node.id, idMap, counter);
44446
- const init = node.init ? "=" + normalizeNode(node.init, idMap, counter) : "";
44447
- return id + init;
44450
+ const init = node.init ? `=${normalizeNode(node.init, idMap, counter)}` : "";
44451
+ return `${id}${init}`;
44448
44452
  }
44449
44453
  if (t.isReturnStatement(node)) {
44450
- return "return " + (node.argument ? normalizeNode(node.argument, idMap, counter) : "");
44454
+ return `return ${node.argument ? normalizeNode(node.argument, idMap, counter) : ""}`;
44451
44455
  }
44452
44456
  if (t.isIfStatement(node)) {
44453
44457
  const test = normalizeNode(node.test, idMap, counter);
44454
44458
  const consequent = normalizeNode(node.consequent, idMap, counter);
44455
- const alternate = node.alternate ? " else " + normalizeNode(node.alternate, idMap, counter) : "";
44459
+ const alternate = node.alternate ? ` else ${normalizeNode(node.alternate, idMap, counter)}` : "";
44456
44460
  return `if(${test})${consequent}${alternate}`;
44457
44461
  }
44458
44462
  if (t.isForStatement(node)) {
@@ -44474,7 +44478,7 @@ function normalizeNode(node, idMap, counter) {
44474
44478
  }
44475
44479
  if (t.isTryStatement(node)) {
44476
44480
  const block = normalizeNode(node.block, idMap, counter);
44477
- const handler = node.handler ? `catch(${normalizeNode(node.handler.param, idMap, counter)})${normalizeNode(node.handler.body, idMap, counter)}` : "";
44481
+ const handler = node.handler ? `catch(${node.handler.param ? normalizeNode(node.handler.param, idMap, counter) : ""})${normalizeNode(node.handler.body, idMap, counter)}` : "";
44478
44482
  const finalizer = node.finalizer ? `finally${normalizeNode(node.finalizer, idMap, counter)}` : "";
44479
44483
  return `try${block}${handler}${finalizer}`;
44480
44484
  }
@@ -44541,7 +44545,7 @@ function normalizeNode(node, idMap, counter) {
44541
44545
  const key = normalizeNode(node.key, idMap, counter);
44542
44546
  const params = node.params.map((p) => normalizeNode(p, idMap, counter)).join(",");
44543
44547
  const body = normalizeNode(node.body, idMap, counter);
44544
- const kind = node.kind !== "method" ? node.kind + " " : "";
44548
+ const kind = node.kind !== "method" ? `${node.kind} ` : "";
44545
44549
  const async = node.async ? "async " : "";
44546
44550
  const static_ = node.static ? "static " : "";
44547
44551
  return `${static_}${async}${kind}${key}(${params})${body}`;
@@ -44582,7 +44586,7 @@ function extractASTBlocks(content, filePath) {
44582
44586
  traverse(ast, {
44583
44587
  FunctionDeclaration(path5) {
44584
44588
  const node = path5.node;
44585
- if (!node.id || !node.loc) return;
44589
+ if (!node.id || !node.loc || node.start === null || node.end === null) return;
44586
44590
  const idMap = /* @__PURE__ */ new Map();
44587
44591
  const counter = { val: 0 };
44588
44592
  const normalized = normalizeNode(node, idMap, counter);
@@ -44602,6 +44606,7 @@ function extractASTBlocks(content, filePath) {
44602
44606
  const node = path5.node;
44603
44607
  for (const decl of node.declarations) {
44604
44608
  if (!t.isIdentifier(decl.id) || !decl.init || !decl.loc) continue;
44609
+ if (decl.start === null || decl.end === null) continue;
44605
44610
  if (!t.isArrowFunctionExpression(decl.init) && !t.isFunctionExpression(decl.init)) continue;
44606
44611
  const idMap = /* @__PURE__ */ new Map();
44607
44612
  const counter = { val: 0 };
@@ -44621,7 +44626,7 @@ function extractASTBlocks(content, filePath) {
44621
44626
  },
44622
44627
  ClassDeclaration(path5) {
44623
44628
  const node = path5.node;
44624
- if (!node.id || !node.loc) return;
44629
+ if (!node.id || !node.loc || node.start === null || node.end === null) return;
44625
44630
  const idMap = /* @__PURE__ */ new Map();
44626
44631
  const counter = { val: 0 };
44627
44632
  const normalized = normalizeNode(node, idMap, counter);
@@ -44639,13 +44644,14 @@ function extractASTBlocks(content, filePath) {
44639
44644
  },
44640
44645
  TSTypeAliasDeclaration(path5) {
44641
44646
  const node = path5.node;
44642
- if (!node.loc) return;
44647
+ if (!node.loc || node.start === null || node.end === null) return;
44648
+ const nodeContent = content.slice(node.start, node.end);
44643
44649
  blocks.push({
44644
44650
  type: "type",
44645
44651
  name: node.id.name,
44646
- content: content.slice(node.start, node.end),
44647
- normalized: content.slice(node.start, node.end).replace(/\s+/g, " "),
44648
- hash: simpleHash2(content.slice(node.start, node.end).replace(/\s+/g, " ")),
44652
+ content: nodeContent,
44653
+ normalized: nodeContent.replace(/\s+/g, " "),
44654
+ hash: simpleHash2(nodeContent.replace(/\s+/g, " ")),
44649
44655
  filePath,
44650
44656
  startLine: node.loc.start.line,
44651
44657
  endLine: node.loc.end.line,
@@ -44654,13 +44660,14 @@ function extractASTBlocks(content, filePath) {
44654
44660
  },
44655
44661
  TSInterfaceDeclaration(path5) {
44656
44662
  const node = path5.node;
44657
- if (!node.loc) return;
44663
+ if (!node.loc || node.start === null || node.end === null) return;
44664
+ const nodeContent = content.slice(node.start, node.end);
44658
44665
  blocks.push({
44659
44666
  type: "interface",
44660
44667
  name: node.id.name,
44661
- content: content.slice(node.start, node.end),
44662
- normalized: content.slice(node.start, node.end).replace(/\s+/g, " "),
44663
- hash: simpleHash2(content.slice(node.start, node.end).replace(/\s+/g, " ")),
44668
+ content: nodeContent,
44669
+ normalized: nodeContent.replace(/\s+/g, " "),
44670
+ hash: simpleHash2(nodeContent.replace(/\s+/g, " ")),
44664
44671
  filePath,
44665
44672
  startLine: node.loc.start.line,
44666
44673
  endLine: node.loc.end.line,
@@ -44672,7 +44679,7 @@ function extractASTBlocks(content, filePath) {
44672
44679
  }
44673
44680
  return blocks;
44674
44681
  }
44675
- function findASTDuplicates(blocks, minSimilarity) {
44682
+ function findASTDuplicates(blocks, _minSimilarity) {
44676
44683
  const groups = [];
44677
44684
  const hashGroups = /* @__PURE__ */ new Map();
44678
44685
  for (const block of blocks) {
@@ -44681,7 +44688,7 @@ function findASTDuplicates(blocks, minSimilarity) {
44681
44688
  hashGroups.set(block.hash, existing);
44682
44689
  }
44683
44690
  let groupId = 0;
44684
- for (const [_hash, matches] of hashGroups) {
44691
+ for (const [, matches] of hashGroups) {
44685
44692
  if (matches.length < 2) continue;
44686
44693
  const uniqueMatches = matches.filter(
44687
44694
  (m, i) => !matches.slice(0, i).some(
@@ -44690,6 +44697,7 @@ function findASTDuplicates(blocks, minSimilarity) {
44690
44697
  );
44691
44698
  if (uniqueMatches.length < 2) continue;
44692
44699
  const firstMatch = uniqueMatches[0];
44700
+ if (!firstMatch) continue;
44693
44701
  groups.push({
44694
44702
  id: groupId++,
44695
44703
  type: firstMatch.type,
@@ -44916,6 +44924,7 @@ function formatASTDuplicates(groups, basePath) {
44916
44924
  lines.push("");
44917
44925
  for (let i = 0; i < Math.min(groups.length, 20); i++) {
44918
44926
  const group = groups[i];
44927
+ if (!group) continue;
44919
44928
  const typeLabel = AST_TYPE_LABELS[group.type] || group.type;
44920
44929
  lines.push(`${bold}${typeLabel} ${i + 1}${reset} \u2502 ${red}${bold}IDENTICAL${reset} \u2502 ${group.matches.length} occurrences`);
44921
44930
  lines.push("");
@@ -44951,6 +44960,10 @@ function formatASTDuplicates(groups, basePath) {
44951
44960
  return lines.join("\n");
44952
44961
  }
44953
44962
 
44963
+ // src/scanner.ts
44964
+ var import_promises2 = require("node:fs/promises");
44965
+ var import_node_path3 = __toESM(require("node:path"), 1);
44966
+
44954
44967
  // node_modules/@isaacs/balanced-match/dist/esm/index.js
44955
44968
  var balanced = (a, b, str) => {
44956
44969
  const ma = a instanceof RegExp ? maybeMatch(a, str) : a;
@@ -51602,8 +51615,6 @@ var glob = Object.assign(glob_, {
51602
51615
  glob.glob = glob;
51603
51616
 
51604
51617
  // src/scanner.ts
51605
- var import_node_path3 = __toESM(require("node:path"), 1);
51606
- var import_promises2 = require("node:fs/promises");
51607
51618
  function simpleHash3(str) {
51608
51619
  let hash = 0;
51609
51620
  for (let i = 0; i < str.length; i++) {
@@ -51713,7 +51724,7 @@ async function scanDirectory(targetPath, options, enableAST = true) {
51713
51724
  }
51714
51725
 
51715
51726
  // index.ts
51716
- var VERSION = process.env.npm_package_version || "1.5.0";
51727
+ var VERSION = process.env.npm_package_version || "1.5.2";
51717
51728
  function parseDiffOutput(diff, cwd) {
51718
51729
  const changes = /* @__PURE__ */ new Map();
51719
51730
  let currentFile = null;
@@ -51726,12 +51737,12 @@ function parseDiffOutput(diff, cwd) {
51726
51737
  }
51727
51738
  } else if (line.startsWith("@@") && currentFile) {
51728
51739
  const match2 = line.match(/@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
51729
- if (match2) {
51740
+ if (match2?.[1]) {
51730
51741
  newLineNum = parseInt(match2[1], 10);
51731
51742
  }
51732
51743
  } else if (currentFile && newLineNum > 0) {
51733
51744
  if (line.startsWith("+") && !line.startsWith("+++")) {
51734
- const ranges = changes.get(currentFile);
51745
+ const ranges = changes.get(currentFile) ?? [];
51735
51746
  const lastRange = ranges[ranges.length - 1];
51736
51747
  if (lastRange && lastRange.end === newLineNum - 1) {
51737
51748
  lastRange.end = newLineNum;
@@ -51752,10 +51763,9 @@ function getChangedLines(targetPath) {
51752
51763
  const allChanges = /* @__PURE__ */ new Map();
51753
51764
  const mergeChanges = (newChanges) => {
51754
51765
  for (const [file, ranges] of newChanges) {
51755
- if (!allChanges.has(file)) {
51756
- allChanges.set(file, []);
51757
- }
51758
- allChanges.get(file).push(...ranges);
51766
+ const existing = allChanges.get(file) ?? [];
51767
+ existing.push(...ranges);
51768
+ allChanges.set(file, existing);
51759
51769
  }
51760
51770
  };
51761
51771
  const addFullFile = (filePath) => {
@@ -51885,7 +51895,7 @@ async function main() {
51885
51895
  }
51886
51896
  console.log(`
51887
51897
  Scanning ${targetPath}...`);
51888
- if (!scanAll) {
51898
+ if (!scanAll && changedLines) {
51889
51899
  console.log(` Mode: checking changed lines in ${changedLines.size} files`);
51890
51900
  } else {
51891
51901
  console.log(` Mode: full codebase scan`);
@@ -51952,7 +51962,7 @@ Scanning ${targetPath}...`);
51952
51962
  return groups.filter((group) => {
51953
51963
  const packages = new Set(
51954
51964
  group.matches.map((m) => {
51955
- const match2 = m.filePath.match(/(?:apps|packages|libs)\/([^\/]+)/);
51965
+ const match2 = m.filePath.match(/(?:apps|packages|libs)\/([^/]+)/);
51956
51966
  return match2 ? match2[1] : m.filePath.split("/")[0];
51957
51967
  })
51958
51968
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dslop",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
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",
@@ -44,6 +44,7 @@
44
44
  "node": ">=18"
45
45
  },
46
46
  "devDependencies": {
47
+ "@types/babel__traverse": "^7.28.0",
47
48
  "@types/bun": "latest",
48
49
  "changelogen": "^0.6.2",
49
50
  "esbuild": "^0.27.2"