deslop-js 0.0.19 → 0.0.20-dev.9342b67
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 +76 -3
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +77 -4
- package/package.json +1 -1
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++;
|