deslop-js 0.0.19-dev.93f3ed2 → 0.0.19-dev.be8757d

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/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ let node_os = require("node:os");
41
41
  node_os = __toESM(node_os, 1);
42
42
  let oxc_resolver = require("oxc-resolver");
43
43
  let minimatch = require("minimatch");
44
+ let node_child_process = require("node:child_process");
44
45
 
45
46
  //#region src/constants.ts
46
47
  const DEFAULT_EXTENSIONS = [
@@ -348,6 +349,7 @@ const DEFAULT_SEMANTIC_TSCONFIG_NAMES = [
348
349
  "tsconfig.src.json",
349
350
  "jsconfig.json"
350
351
  ];
352
+ const GIT_CHECK_IGNORE_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
351
353
 
352
354
  //#endregion
353
355
  //#region src/errors.ts
@@ -7177,7 +7179,8 @@ const buildDependencyGraph = (inputs) => {
7177
7179
  isTestEntry: input.isTestEntry,
7178
7180
  isReachable: false,
7179
7181
  isDeclarationFile: input.fileId.path.endsWith(".d.ts") || input.fileId.path.endsWith(".d.mts") || input.fileId.path.endsWith(".d.cts"),
7180
- isConfigFile: isConfigFile(input.fileId.path)
7182
+ isConfigFile: isConfigFile(input.fileId.path),
7183
+ isGitIgnored: input.isGitIgnored
7181
7184
  }));
7182
7185
  const edges = [];
7183
7186
  const reverseEdges = /* @__PURE__ */ new Map();
@@ -7514,6 +7517,7 @@ const detectOrphanFiles = (graph) => {
7514
7517
  if (module.isEntryPoint) continue;
7515
7518
  if (module.isDeclarationFile) continue;
7516
7519
  if (module.isConfigFile) continue;
7520
+ if (module.isGitIgnored) continue;
7517
7521
  if (hasExcludedExtension(module.fileId.path)) continue;
7518
7522
  if (isExcludedByPattern(module.fileId.path)) continue;
7519
7523
  if (isOpaqueToAnalysis(module)) continue;
@@ -7548,6 +7552,7 @@ const detectDeadExports = (graph, config) => {
7548
7552
  for (const module of graph.modules) {
7549
7553
  if (!module.isReachable) continue;
7550
7554
  if (module.isDeclarationFile) continue;
7555
+ if (module.isGitIgnored) continue;
7551
7556
  if (module.isEntryPoint && !config.includeEntryExports) continue;
7552
7557
  const defaultExportLinkedNames = /* @__PURE__ */ new Set();
7553
7558
  for (const exportInfo of module.exports) if (exportInfo.isDefault && exportInfo.defaultExportLocalName && usageMap.has(`${module.fileId.path}::default`)) defaultExportLinkedNames.add(exportInfo.defaultExportLocalName);
@@ -12482,6 +12487,64 @@ const generateReport = (graph, config) => {
12482
12487
  };
12483
12488
  };
12484
12489
 
12490
+ //#endregion
12491
+ //#region src/utils/collect-git-ignored-paths.ts
12492
+ /**
12493
+ * Returns the subset of `candidatePaths` that git considers ignored.
12494
+ *
12495
+ * `--no-index` is load-bearing: without it `git check-ignore` stays silent for
12496
+ * any path already tracked in the index, so generated files that were committed
12497
+ * once (then later gitignored) would not be reported. We want the ignore *rules*
12498
+ * to decide, independent of tracking state.
12499
+ *
12500
+ * `core.excludesFile=<devNull>` scopes the result to the analyzed project's own
12501
+ * ignore rules: a developer's personal global gitignore must not change which
12502
+ * files deslop reports, otherwise the same project yields different findings on
12503
+ * different machines.
12504
+ *
12505
+ * `gitUnavailable` is true only when the `git` binary could not be launched
12506
+ * (e.g. not installed → `ENOENT`). A non-git directory is normal: git exits 128
12507
+ * and often closes stdin before reading it, which surfaces as an `EPIPE` on the
12508
+ * input write — that is reported as available-but-empty, not a failure. Every
12509
+ * failure still degrades to an empty set so callers never crash. Exit status 1
12510
+ * means "no matches", not an error.
12511
+ */
12512
+ const collectGitIgnoredPaths = (rootDirectory, candidatePaths) => {
12513
+ if (candidatePaths.length === 0) return {
12514
+ ignoredPaths: /* @__PURE__ */ new Set(),
12515
+ gitUnavailable: false
12516
+ };
12517
+ const result = (0, node_child_process.spawnSync)("git", [
12518
+ "-c",
12519
+ `core.excludesFile=${node_os.devNull}`,
12520
+ "check-ignore",
12521
+ "--no-index",
12522
+ "--stdin",
12523
+ "-z"
12524
+ ], {
12525
+ cwd: rootDirectory,
12526
+ input: candidatePaths.join("\0"),
12527
+ encoding: "utf-8",
12528
+ maxBuffer: GIT_CHECK_IGNORE_MAX_BUFFER_BYTES
12529
+ });
12530
+ if (result.error) {
12531
+ const gitBinaryMissing = "code" in result.error && result.error.code === "ENOENT";
12532
+ return {
12533
+ ignoredPaths: /* @__PURE__ */ new Set(),
12534
+ gitUnavailable: gitBinaryMissing
12535
+ };
12536
+ }
12537
+ if (result.status === null || result.status > 1) return {
12538
+ ignoredPaths: /* @__PURE__ */ new Set(),
12539
+ gitUnavailable: false
12540
+ };
12541
+ const ignoredPaths = result.stdout.split("\0").filter((entry) => entry.length > 0);
12542
+ return {
12543
+ ignoredPaths: new Set(ignoredPaths),
12544
+ gitUnavailable: false
12545
+ };
12546
+ };
12547
+
12485
12548
  //#endregion
12486
12549
  //#region src/index.ts
12487
12550
  const STYLE_EXTENSIONS = [".css", ".scss"];
@@ -12767,6 +12830,14 @@ const analyze = async (config) => {
12767
12830
  const productionEntrySet = new Set(discoveredEntries.productionEntries);
12768
12831
  const testEntrySet = new Set(discoveredEntries.testEntries);
12769
12832
  const alwaysUsedFileSet = new Set(discoveredEntries.alwaysUsedFiles);
12833
+ const gitIgnoreResult = collectGitIgnoredPaths((0, node_path.resolve)(config.rootDir), files.map((file) => file.path));
12834
+ const gitIgnoredFileSet = gitIgnoreResult.ignoredPaths;
12835
+ if (gitIgnoreResult.gitUnavailable) setupErrors.push(new WorkspaceError({
12836
+ code: "gitignore-check-failed",
12837
+ severity: "info",
12838
+ message: "git unavailable — .gitignore filtering skipped",
12839
+ path: config.rootDir
12840
+ }));
12770
12841
  let hasReactNative = false;
12771
12842
  try {
12772
12843
  hasReactNative = detectReactNative(config.rootDir, workspacePackages);
@@ -12855,7 +12926,8 @@ const analyze = async (config) => {
12855
12926
  parsed: parsedModule,
12856
12927
  resolvedImports: resolvedImportMap,
12857
12928
  isEntryPoint: isAlwaysUsed || productionEntrySet.has(file.path) || testEntrySet.has(file.path),
12858
- isTestEntry: testEntrySet.has(file.path)
12929
+ isTestEntry: testEntrySet.has(file.path),
12930
+ isGitIgnored: gitIgnoredFileSet.has(file.path)
12859
12931
  });
12860
12932
  }
12861
12933
  const discoveredFilePaths = new Set(files.map((file) => file.path));
@@ -12901,7 +12973,8 @@ const analyze = async (config) => {
12901
12973
  parsed: parsedStyleModule,
12902
12974
  resolvedImports: resolvedStyleImportMap,
12903
12975
  isEntryPoint: false,
12904
- isTestEntry: false
12976
+ isTestEntry: false,
12977
+ isGitIgnored: gitIgnoredFileSet.has(styleFilePath)
12905
12978
  });
12906
12979
  discoveredFilePaths.add(styleFilePath);
12907
12980
  nextFileIndex++;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region src/errors.d.ts
2
- type DeslopErrorCode = "file-read-failed" | "file-too-large" | "file-empty" | "file-binary" | "file-minified" | "parse-failed" | "parse-recovered" | "parse-recovered-partial" | "ast-walk-failed" | "ast-walk-depth-exceeded" | "tsconfig-not-found" | "tsconfig-parse-failed" | "ts-program-creation-failed" | "ts-program-too-large" | "ts-not-loadable" | "package-json-not-found" | "package-json-parse-failed" | "workspace-discovery-failed" | "resolver-init-failed" | "monorepo-discovery-failed" | "detector-failed" | "config-invalid" | "system-out-of-memory" | "unknown";
2
+ type DeslopErrorCode = "file-read-failed" | "file-too-large" | "file-empty" | "file-binary" | "file-minified" | "parse-failed" | "parse-recovered" | "parse-recovered-partial" | "ast-walk-failed" | "ast-walk-depth-exceeded" | "tsconfig-not-found" | "tsconfig-parse-failed" | "ts-program-creation-failed" | "ts-program-too-large" | "ts-not-loadable" | "package-json-not-found" | "package-json-parse-failed" | "workspace-discovery-failed" | "gitignore-check-failed" | "resolver-init-failed" | "monorepo-discovery-failed" | "detector-failed" | "config-invalid" | "system-out-of-memory" | "unknown";
3
3
  type DeslopErrorModule = "collect" | "parse" | "linker" | "resolver" | "report" | "semantic" | "config";
4
4
  type DeslopErrorSeverity = "fatal" | "warning" | "info";
5
5
  interface DeslopErrorInput {
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region src/errors.d.ts
2
- type DeslopErrorCode = "file-read-failed" | "file-too-large" | "file-empty" | "file-binary" | "file-minified" | "parse-failed" | "parse-recovered" | "parse-recovered-partial" | "ast-walk-failed" | "ast-walk-depth-exceeded" | "tsconfig-not-found" | "tsconfig-parse-failed" | "ts-program-creation-failed" | "ts-program-too-large" | "ts-not-loadable" | "package-json-not-found" | "package-json-parse-failed" | "workspace-discovery-failed" | "resolver-init-failed" | "monorepo-discovery-failed" | "detector-failed" | "config-invalid" | "system-out-of-memory" | "unknown";
2
+ type DeslopErrorCode = "file-read-failed" | "file-too-large" | "file-empty" | "file-binary" | "file-minified" | "parse-failed" | "parse-recovered" | "parse-recovered-partial" | "ast-walk-failed" | "ast-walk-depth-exceeded" | "tsconfig-not-found" | "tsconfig-parse-failed" | "ts-program-creation-failed" | "ts-program-too-large" | "ts-not-loadable" | "package-json-not-found" | "package-json-parse-failed" | "workspace-discovery-failed" | "gitignore-check-failed" | "resolver-init-failed" | "monorepo-discovery-failed" | "detector-failed" | "config-invalid" | "system-out-of-memory" | "unknown";
3
3
  type DeslopErrorModule = "collect" | "parse" | "linker" | "resolver" | "report" | "semantic" | "config";
4
4
  type DeslopErrorSeverity = "fatal" | "warning" | "info";
5
5
  interface DeslopErrorInput {
package/dist/index.mjs CHANGED
@@ -6,9 +6,10 @@ import { parseSync } from "oxc-parser";
6
6
  import ts from "typescript";
7
7
  import { Worker } from "node:worker_threads";
8
8
  import { fileURLToPath } from "node:url";
9
- import os from "node:os";
9
+ import os, { devNull } from "node:os";
10
10
  import { ResolverFactory } from "oxc-resolver";
11
11
  import { minimatch } from "minimatch";
12
+ import { spawnSync } from "node:child_process";
12
13
 
13
14
  //#region src/constants.ts
14
15
  const DEFAULT_EXTENSIONS = [
@@ -316,6 +317,7 @@ const DEFAULT_SEMANTIC_TSCONFIG_NAMES = [
316
317
  "tsconfig.src.json",
317
318
  "jsconfig.json"
318
319
  ];
320
+ const GIT_CHECK_IGNORE_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
319
321
 
320
322
  //#endregion
321
323
  //#region src/errors.ts
@@ -7145,7 +7147,8 @@ const buildDependencyGraph = (inputs) => {
7145
7147
  isTestEntry: input.isTestEntry,
7146
7148
  isReachable: false,
7147
7149
  isDeclarationFile: input.fileId.path.endsWith(".d.ts") || input.fileId.path.endsWith(".d.mts") || input.fileId.path.endsWith(".d.cts"),
7148
- isConfigFile: isConfigFile(input.fileId.path)
7150
+ isConfigFile: isConfigFile(input.fileId.path),
7151
+ isGitIgnored: input.isGitIgnored
7149
7152
  }));
7150
7153
  const edges = [];
7151
7154
  const reverseEdges = /* @__PURE__ */ new Map();
@@ -7482,6 +7485,7 @@ const detectOrphanFiles = (graph) => {
7482
7485
  if (module.isEntryPoint) continue;
7483
7486
  if (module.isDeclarationFile) continue;
7484
7487
  if (module.isConfigFile) continue;
7488
+ if (module.isGitIgnored) continue;
7485
7489
  if (hasExcludedExtension(module.fileId.path)) continue;
7486
7490
  if (isExcludedByPattern(module.fileId.path)) continue;
7487
7491
  if (isOpaqueToAnalysis(module)) continue;
@@ -7516,6 +7520,7 @@ const detectDeadExports = (graph, config) => {
7516
7520
  for (const module of graph.modules) {
7517
7521
  if (!module.isReachable) continue;
7518
7522
  if (module.isDeclarationFile) continue;
7523
+ if (module.isGitIgnored) continue;
7519
7524
  if (module.isEntryPoint && !config.includeEntryExports) continue;
7520
7525
  const defaultExportLinkedNames = /* @__PURE__ */ new Set();
7521
7526
  for (const exportInfo of module.exports) if (exportInfo.isDefault && exportInfo.defaultExportLocalName && usageMap.has(`${module.fileId.path}::default`)) defaultExportLinkedNames.add(exportInfo.defaultExportLocalName);
@@ -12450,6 +12455,64 @@ const generateReport = (graph, config) => {
12450
12455
  };
12451
12456
  };
12452
12457
 
12458
+ //#endregion
12459
+ //#region src/utils/collect-git-ignored-paths.ts
12460
+ /**
12461
+ * Returns the subset of `candidatePaths` that git considers ignored.
12462
+ *
12463
+ * `--no-index` is load-bearing: without it `git check-ignore` stays silent for
12464
+ * any path already tracked in the index, so generated files that were committed
12465
+ * once (then later gitignored) would not be reported. We want the ignore *rules*
12466
+ * to decide, independent of tracking state.
12467
+ *
12468
+ * `core.excludesFile=<devNull>` scopes the result to the analyzed project's own
12469
+ * ignore rules: a developer's personal global gitignore must not change which
12470
+ * files deslop reports, otherwise the same project yields different findings on
12471
+ * different machines.
12472
+ *
12473
+ * `gitUnavailable` is true only when the `git` binary could not be launched
12474
+ * (e.g. not installed → `ENOENT`). A non-git directory is normal: git exits 128
12475
+ * and often closes stdin before reading it, which surfaces as an `EPIPE` on the
12476
+ * input write — that is reported as available-but-empty, not a failure. Every
12477
+ * failure still degrades to an empty set so callers never crash. Exit status 1
12478
+ * means "no matches", not an error.
12479
+ */
12480
+ const collectGitIgnoredPaths = (rootDirectory, candidatePaths) => {
12481
+ if (candidatePaths.length === 0) return {
12482
+ ignoredPaths: /* @__PURE__ */ new Set(),
12483
+ gitUnavailable: false
12484
+ };
12485
+ const result = spawnSync("git", [
12486
+ "-c",
12487
+ `core.excludesFile=${devNull}`,
12488
+ "check-ignore",
12489
+ "--no-index",
12490
+ "--stdin",
12491
+ "-z"
12492
+ ], {
12493
+ cwd: rootDirectory,
12494
+ input: candidatePaths.join("\0"),
12495
+ encoding: "utf-8",
12496
+ maxBuffer: GIT_CHECK_IGNORE_MAX_BUFFER_BYTES
12497
+ });
12498
+ if (result.error) {
12499
+ const gitBinaryMissing = "code" in result.error && result.error.code === "ENOENT";
12500
+ return {
12501
+ ignoredPaths: /* @__PURE__ */ new Set(),
12502
+ gitUnavailable: gitBinaryMissing
12503
+ };
12504
+ }
12505
+ if (result.status === null || result.status > 1) return {
12506
+ ignoredPaths: /* @__PURE__ */ new Set(),
12507
+ gitUnavailable: false
12508
+ };
12509
+ const ignoredPaths = result.stdout.split("\0").filter((entry) => entry.length > 0);
12510
+ return {
12511
+ ignoredPaths: new Set(ignoredPaths),
12512
+ gitUnavailable: false
12513
+ };
12514
+ };
12515
+
12453
12516
  //#endregion
12454
12517
  //#region src/index.ts
12455
12518
  const STYLE_EXTENSIONS = [".css", ".scss"];
@@ -12735,6 +12798,14 @@ const analyze = async (config) => {
12735
12798
  const productionEntrySet = new Set(discoveredEntries.productionEntries);
12736
12799
  const testEntrySet = new Set(discoveredEntries.testEntries);
12737
12800
  const alwaysUsedFileSet = new Set(discoveredEntries.alwaysUsedFiles);
12801
+ const gitIgnoreResult = collectGitIgnoredPaths(resolve(config.rootDir), files.map((file) => file.path));
12802
+ const gitIgnoredFileSet = gitIgnoreResult.ignoredPaths;
12803
+ if (gitIgnoreResult.gitUnavailable) setupErrors.push(new WorkspaceError({
12804
+ code: "gitignore-check-failed",
12805
+ severity: "info",
12806
+ message: "git unavailable — .gitignore filtering skipped",
12807
+ path: config.rootDir
12808
+ }));
12738
12809
  let hasReactNative = false;
12739
12810
  try {
12740
12811
  hasReactNative = detectReactNative(config.rootDir, workspacePackages);
@@ -12823,7 +12894,8 @@ const analyze = async (config) => {
12823
12894
  parsed: parsedModule,
12824
12895
  resolvedImports: resolvedImportMap,
12825
12896
  isEntryPoint: isAlwaysUsed || productionEntrySet.has(file.path) || testEntrySet.has(file.path),
12826
- isTestEntry: testEntrySet.has(file.path)
12897
+ isTestEntry: testEntrySet.has(file.path),
12898
+ isGitIgnored: gitIgnoredFileSet.has(file.path)
12827
12899
  });
12828
12900
  }
12829
12901
  const discoveredFilePaths = new Set(files.map((file) => file.path));
@@ -12869,7 +12941,8 @@ const analyze = async (config) => {
12869
12941
  parsed: parsedStyleModule,
12870
12942
  resolvedImports: resolvedStyleImportMap,
12871
12943
  isEntryPoint: false,
12872
- isTestEntry: false
12944
+ isTestEntry: false,
12945
+ isGitIgnored: gitIgnoredFileSet.has(styleFilePath)
12873
12946
  });
12874
12947
  discoveredFilePaths.add(styleFilePath);
12875
12948
  nextFileIndex++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deslop-js",
3
- "version": "0.0.19-dev.93f3ed2",
3
+ "version": "0.0.19-dev.be8757d",
4
4
  "description": "Deslop JavaScript code",
5
5
  "keywords": [
6
6
  "dead-code",