dslop 1.7.1 → 1.7.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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.7.2
4
+
5
+ [compare changes](https://github.com/turf-sports/dslop/compare/v1.7.1...v1.7.2)
6
+
7
+ ### 🔥 Performance
8
+
9
+ - 50x faster - parallel processing, cache line counts ([de430e5](https://github.com/turf-sports/dslop/commit/de430e5))
10
+
11
+ ### 📖 Documentation
12
+
13
+ - Update performance numbers ([1c6465e](https://github.com/turf-sports/dslop/commit/1c6465e))
14
+
15
+ ### ❤️ Contributors
16
+
17
+ - Siddharth Sharma <sharmasiddharthcs@gmail.com>
18
+
3
19
  ## v1.7.1
4
20
 
5
21
  [compare changes](https://github.com/turf-sports/dslop/compare/v1.7.0...v1.7.1)
package/README.md CHANGED
@@ -151,23 +151,19 @@ function getInitials(person: Person): string {
151
151
 
152
152
  ## Performance
153
153
 
154
- dslop uses aggressive caching to make subsequent runs fast:
154
+ dslop uses aggressive caching + parallel processing for speed:
155
155
 
156
156
  ```bash
157
157
  # First run (builds cache)
158
158
  $ dslop --all
159
- Scanned 1410 files in 2862ms
160
- Cache: 0 hits, 1410 misses (0% hit rate)
159
+ Scanned 1410 files (373K lines) in 2.5s
161
160
 
162
- # Second run (uses cache)
161
+ # Cached runs
163
162
  $ dslop --all
164
- Scanned 1410 files in 305ms
165
- Cache: 1410 hits, 0 misses (100% hit rate)
163
+ Scanned 1410 files (373K lines) in 170ms # 50x faster
166
164
  ```
167
165
 
168
- Cache is stored in `.dslop-cache` in your project root. Add it to `.gitignore`.
169
-
170
- Use `--no-cache` to bypass the cache.
166
+ Cache is stored in `.dslop-cache`. Add it to `.gitignore`. Use `--no-cache` to bypass.
171
167
 
172
168
  ## Limitations
173
169
 
package/dist/index.cjs CHANGED
@@ -50675,7 +50675,7 @@ glob.glob = glob;
50675
50675
  // src/cache.ts
50676
50676
  var import_promises2 = require("node:fs/promises");
50677
50677
  var import_node_path3 = __toESM(require("node:path"), 1);
50678
- var CACHE_VERSION = 2;
50678
+ var CACHE_VERSION = 3;
50679
50679
  var CACHE_FILE = ".dslop-cache";
50680
50680
  var ASTCache = class {
50681
50681
  cache = { version: CACHE_VERSION, files: {} };
@@ -50699,70 +50699,55 @@ var ASTCache = class {
50699
50699
  this.cache = { version: CACHE_VERSION, files: {} };
50700
50700
  }
50701
50701
  }
50702
- async get(filePath) {
50702
+ // Check cache with pre-fetched stat
50703
+ get(filePath, mtime, size) {
50703
50704
  const cached = this.cache.files[filePath];
50704
50705
  if (!cached) {
50705
50706
  this.misses++;
50706
50707
  return null;
50707
50708
  }
50708
- try {
50709
- const fileStat = await (0, import_promises2.stat)(filePath);
50710
- if (fileStat.mtimeMs !== cached.mtime || fileStat.size !== cached.size) {
50711
- this.misses++;
50712
- return null;
50713
- }
50714
- this.hits++;
50715
- return cached.blocks.map((b) => ({
50709
+ if (mtime !== cached.mtime || size !== cached.size) {
50710
+ this.misses++;
50711
+ return null;
50712
+ }
50713
+ this.hits++;
50714
+ return {
50715
+ lineCount: cached.lineCount,
50716
+ blocks: cached.blocks.map((b) => ({
50716
50717
  type: b.type,
50717
50718
  name: b.name,
50718
50719
  content: "",
50719
- // Don't cache content - we can re-read if needed
50720
50720
  normalized: b.normalized,
50721
50721
  hash: b.hash,
50722
50722
  filePath,
50723
50723
  startLine: b.startLine,
50724
50724
  endLine: b.endLine,
50725
50725
  exported: b.exported
50726
- }));
50727
- } catch {
50728
- this.misses++;
50729
- return null;
50730
- }
50726
+ }))
50727
+ };
50731
50728
  }
50732
- async set(filePath, blocks) {
50733
- try {
50734
- const fileStat = await (0, import_promises2.stat)(filePath);
50735
- this.cache.files[filePath] = {
50736
- mtime: fileStat.mtimeMs,
50737
- size: fileStat.size,
50738
- blocks: blocks.map((b) => ({
50739
- type: b.type,
50740
- name: b.name,
50741
- hash: b.hash,
50742
- normalized: b.normalized,
50743
- startLine: b.startLine,
50744
- endLine: b.endLine,
50745
- exported: b.exported
50746
- }))
50747
- };
50748
- this.dirty = true;
50749
- } catch {
50750
- }
50729
+ set(filePath, blocks, lineCount, mtime, size) {
50730
+ this.cache.files[filePath] = {
50731
+ mtime,
50732
+ size,
50733
+ lineCount,
50734
+ blocks: blocks.map((b) => ({
50735
+ type: b.type,
50736
+ name: b.name,
50737
+ hash: b.hash,
50738
+ normalized: b.normalized,
50739
+ startLine: b.startLine,
50740
+ endLine: b.endLine,
50741
+ exported: b.exported
50742
+ }))
50743
+ };
50744
+ this.dirty = true;
50751
50745
  }
50752
50746
  async save() {
50753
50747
  if (!this.dirty) return;
50754
50748
  try {
50755
50749
  const dir = import_node_path3.default.dirname(this.cachePath);
50756
50750
  await (0, import_promises2.mkdir)(dir, { recursive: true });
50757
- const prunedFiles = {};
50758
- for (const [filePath, cached] of Object.entries(this.cache.files)) {
50759
- try {
50760
- await (0, import_promises2.stat)(filePath);
50761
- prunedFiles[filePath] = cached;
50762
- } catch {
50763
- }
50764
- }
50765
- this.cache.files = prunedFiles;
50766
50751
  await (0, import_promises2.writeFile)(this.cachePath, JSON.stringify(this.cache), "utf-8");
50767
50752
  } catch {
50768
50753
  }
@@ -50779,6 +50764,7 @@ var ASTCache = class {
50779
50764
 
50780
50765
  // src/scanner.ts
50781
50766
  var MAX_FILE_SIZE = 1024 * 1024;
50767
+ var PARALLEL_BATCH_SIZE = 50;
50782
50768
  function shouldIgnore(filePath, ignorePatterns) {
50783
50769
  const normalizedPath = filePath.toLowerCase();
50784
50770
  return ignorePatterns.some((pattern) => {
@@ -50786,6 +50772,25 @@ function shouldIgnore(filePath, ignorePatterns) {
50786
50772
  return normalizedPath.includes(`/${normalizedPattern}/`) || normalizedPath.includes(`\\${normalizedPattern}\\`) || normalizedPath.endsWith(`/${normalizedPattern}`) || normalizedPath.endsWith(`\\${normalizedPattern}`);
50787
50773
  });
50788
50774
  }
50775
+ async function processFile(filePath, cache) {
50776
+ try {
50777
+ const fileStat = await (0, import_promises3.stat)(filePath);
50778
+ if (fileStat.size > MAX_FILE_SIZE) {
50779
+ return null;
50780
+ }
50781
+ const cached = cache?.get(filePath, fileStat.mtimeMs, fileStat.size);
50782
+ if (cached) {
50783
+ return cached;
50784
+ }
50785
+ const content = await (0, import_promises3.readFile)(filePath, "utf-8");
50786
+ const lineCount = content.split("\n").length;
50787
+ const blocks = extractASTBlocks(content, filePath);
50788
+ cache?.set(filePath, blocks, lineCount, fileStat.mtimeMs, fileStat.size);
50789
+ return { blocks, lineCount };
50790
+ } catch {
50791
+ return null;
50792
+ }
50793
+ }
50789
50794
  async function scanDirectory(targetPath, options, useCache = true) {
50790
50795
  const { extensions, ignorePatterns } = options;
50791
50796
  const absolutePath = import_node_path4.default.resolve(targetPath);
@@ -50797,41 +50802,28 @@ async function scanDirectory(targetPath, options, useCache = true) {
50797
50802
  await cache.load();
50798
50803
  }
50799
50804
  const pattern = extensions.length === 1 ? `**/*.${extensions[0]}` : `**/*.{${extensions.join(",")}}`;
50800
- const files = await glob(pattern, {
50805
+ const allFiles = await glob(pattern, {
50801
50806
  cwd: absolutePath,
50802
50807
  absolute: true,
50803
50808
  nodir: true,
50804
50809
  ignore: ignorePatterns.map((p) => `**/${p}/**`)
50805
50810
  });
50806
- for (const filePath of files) {
50807
- if (shouldIgnore(filePath, ignorePatterns)) {
50808
- continue;
50809
- }
50810
- if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx") && !filePath.endsWith(".js") && !filePath.endsWith(".jsx")) {
50811
- continue;
50812
- }
50813
- try {
50814
- const fileStat = await (0, import_promises3.stat)(filePath);
50815
- if (fileStat.size > MAX_FILE_SIZE) {
50816
- continue;
50817
- }
50818
- const cachedAST = cache ? await cache.get(filePath) : null;
50819
- if (cachedAST) {
50820
- astBlocks.push(...cachedAST);
50821
- const content = await (0, import_promises3.readFile)(filePath, "utf-8");
50822
- totalLines += content.split("\n").length;
50823
- fileCount++;
50824
- } else {
50825
- const content = await (0, import_promises3.readFile)(filePath, "utf-8");
50826
- totalLines += content.split("\n").length;
50811
+ const files = allFiles.filter((filePath) => {
50812
+ if (shouldIgnore(filePath, ignorePatterns)) return false;
50813
+ const ext2 = import_node_path4.default.extname(filePath);
50814
+ return ext2 === ".ts" || ext2 === ".tsx" || ext2 === ".js" || ext2 === ".jsx";
50815
+ });
50816
+ for (let i = 0; i < files.length; i += PARALLEL_BATCH_SIZE) {
50817
+ const batch = files.slice(i, i + PARALLEL_BATCH_SIZE);
50818
+ const results = await Promise.all(
50819
+ batch.map((filePath) => processFile(filePath, cache))
50820
+ );
50821
+ for (const result of results) {
50822
+ if (result) {
50823
+ astBlocks.push(...result.blocks);
50824
+ totalLines += result.lineCount;
50827
50825
  fileCount++;
50828
- const fileAST = extractASTBlocks(content, filePath);
50829
- astBlocks.push(...fileAST);
50830
- if (cache) {
50831
- await cache.set(filePath, fileAST);
50832
- }
50833
50826
  }
50834
- } catch {
50835
50827
  }
50836
50828
  }
50837
50829
  if (cache) {
@@ -50846,7 +50838,7 @@ async function scanDirectory(targetPath, options, useCache = true) {
50846
50838
  }
50847
50839
 
50848
50840
  // index.ts
50849
- var VERSION = process.env.npm_package_version || "1.7.1";
50841
+ var VERSION = process.env.npm_package_version || "1.7.2";
50850
50842
  var DEFAULT_EXTENSIONS = ["ts", "tsx", "js", "jsx"];
50851
50843
  var DEFAULT_IGNORE_PATTERNS = [
50852
50844
  "node_modules",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dslop",
3
- "version": "1.7.1",
3
+ "version": "1.7.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",