deslop-js 0.9.5 → 0.9.6

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/README.md CHANGED
@@ -89,6 +89,7 @@ const result = await analyze(config);
89
89
  result.unusedFiles; // files unreachable from any entry point
90
90
  result.unusedExports; // exported symbols never imported
91
91
  result.unusedDependencies; // package.json deps not imported anywhere
92
+ result.skippedDependencies; // declared deps conservatively excluded from unused-dependency analysis
92
93
  result.circularDependencies; // import cycles
93
94
 
94
95
  // redundancy / DRY findings (syntactic, on by default)
@@ -136,6 +137,11 @@ result.totalExports;
136
137
  result.analysisTimeMs;
137
138
  ```
138
139
 
140
+ `skippedDependencies` makes conservative unused-dependency exemptions explicit. It includes
141
+ packages matched by the framework/tooling name allowlist and installed packages that provide a
142
+ binary, because those uses can occur outside source imports and package scripts. A clean
143
+ `unusedDependencies` result is not a verdict for those packages.
144
+
139
145
  ## Programmatic Options
140
146
 
141
147
  `defineConfig` accepts a required `rootDir` and optional overrides:
package/dist/index.cjs CHANGED
@@ -7986,7 +7986,10 @@ const detectStalePackages = (graph, config, summaryCache) => {
7986
7986
  const content = (0, node_fs.readFileSync)(packageJsonPath, "utf-8");
7987
7987
  packageJson = JSON.parse(content);
7988
7988
  } catch {
7989
- return [];
7989
+ return {
7990
+ unusedDependencies: [],
7991
+ skippedDependencies: []
7992
+ };
7990
7993
  }
7991
7994
  const dependencies = packageJson.dependencies ?? {};
7992
7995
  const devDependencies = packageJson.devDependencies ?? {};
@@ -7994,7 +7997,12 @@ const detectStalePackages = (graph, config, summaryCache) => {
7994
7997
  for (const dependencyName of Object.keys(dependencies)) declaredDependencies.set(dependencyName, false);
7995
7998
  for (const dependencyName of Object.keys(devDependencies)) declaredDependencies.set(dependencyName, true);
7996
7999
  const declaredNames = new Set(declaredDependencies.keys());
7997
- const usedPackageNames = collectUsedPackages(graph);
8000
+ const observedPackageNames = collectUsedPackages(graph);
8001
+ const usedPackageNames = new Set(observedPackageNames);
8002
+ const markPackageUsed = (packageName) => {
8003
+ observedPackageNames.add(packageName);
8004
+ usedPackageNames.add(packageName);
8005
+ };
7998
8006
  const monorepoRoot = findMonorepoRoot(config.rootDir);
7999
8007
  const nodeModulesSearchRoots = monorepoRoot && monorepoRoot !== config.rootDir ? [config.rootDir, monorepoRoot] : [config.rootDir];
8000
8008
  const allPackageJsonPaths = discoverAllPackageJsonPaths(config.rootDir, summaryCache);
@@ -8006,29 +8014,29 @@ const detectStalePackages = (graph, config, summaryCache) => {
8006
8014
  for (const packageName of packagesProvidingBinary) usedPackageNames.add(packageName);
8007
8015
  for (const workspacePackageJsonPath of allPackageJsonPaths) {
8008
8016
  const scriptReferenced = collectScriptReferencedPackages(workspacePackageJsonPath, declaredNames, binToPackage);
8009
- for (const packageName of scriptReferenced) usedPackageNames.add(packageName);
8017
+ for (const packageName of scriptReferenced) markPackageUsed(packageName);
8010
8018
  const packageJsonConfigReferenced = collectPackageJsonConfigReferences(workspacePackageJsonPath, declaredNames);
8011
- for (const packageName of packageJsonConfigReferenced) usedPackageNames.add(packageName);
8019
+ for (const packageName of packageJsonConfigReferenced) markPackageUsed(packageName);
8012
8020
  }
8013
8021
  const nxProjectReferenced = collectNxProjectJsonReferences(config.rootDir, declaredNames, binToPackage, summaryCache);
8014
- for (const packageName of nxProjectReferenced) usedPackageNames.add(packageName);
8022
+ for (const packageName of nxProjectReferenced) markPackageUsed(packageName);
8015
8023
  const configSearchRoots = monorepoRoot && monorepoRoot !== config.rootDir ? [config.rootDir, monorepoRoot] : [config.rootDir];
8016
8024
  for (const configSearchRoot of configSearchRoots) {
8017
8025
  const configReferenced = collectConfigReferencedPackages(configSearchRoot, graph, declaredNames, summaryCache);
8018
- for (const packageName of configReferenced) usedPackageNames.add(packageName);
8026
+ for (const packageName of configReferenced) markPackageUsed(packageName);
8019
8027
  const tsconfigReferenced = collectTsconfigReferencedPackages(configSearchRoot, summaryCache);
8020
- for (const packageName of tsconfigReferenced) usedPackageNames.add(packageName);
8028
+ for (const packageName of tsconfigReferenced) markPackageUsed(packageName);
8021
8029
  const { packageNames: expoPluginPackageNames } = extractExpoConfigPluginEntries(configSearchRoot, {
8022
8030
  ...dependencies,
8023
8031
  ...devDependencies
8024
8032
  });
8025
- for (const packageName of expoPluginPackageNames) if (declaredNames.has(packageName)) usedPackageNames.add(packageName);
8033
+ for (const packageName of expoPluginPackageNames) if (declaredNames.has(packageName)) markPackageUsed(packageName);
8026
8034
  }
8027
8035
  if (hasJsxFiles(graph)) {
8028
- if (declaredNames.has("react")) usedPackageNames.add("react");
8029
- if (declaredNames.has("react-dom")) usedPackageNames.add("react-dom");
8030
- if (declaredNames.has("react-native")) usedPackageNames.add("react-native");
8031
- if (declaredNames.has("react-native-web")) usedPackageNames.add("react-native-web");
8036
+ if (declaredNames.has("react")) markPackageUsed("react");
8037
+ if (declaredNames.has("react-dom")) markPackageUsed("react-dom");
8038
+ if (declaredNames.has("react-native")) markPackageUsed("react-native");
8039
+ if (declaredNames.has("react-native-web")) markPackageUsed("react-native-web");
8032
8040
  }
8033
8041
  if (declaredNames.has("react-dom")) {
8034
8042
  if ([
@@ -8043,26 +8051,37 @@ const detectStalePackages = (graph, config, summaryCache) => {
8043
8051
  "@tanstack/react-router",
8044
8052
  "@tanstack/react-start",
8045
8053
  "react-app-rewired"
8046
- ].some((framework) => declaredNames.has(framework) || usedPackageNames.has(framework))) usedPackageNames.add("react-dom");
8054
+ ].some((framework) => declaredNames.has(framework) || usedPackageNames.has(framework))) markPackageUsed("react-dom");
8047
8055
  }
8048
- if (declaredNames.has("astro") && declaredNames.has("sharp")) usedPackageNames.add("sharp");
8056
+ if (declaredNames.has("astro") && declaredNames.has("sharp")) markPackageUsed("sharp");
8049
8057
  if (declaredNames.has("react") && declaredNames.has("react-dom")) {
8050
8058
  const packageJsonPath = (0, node_path.resolve)(config.rootDir, "package.json");
8051
8059
  try {
8052
8060
  const content = (0, node_fs.readFileSync)(packageJsonPath, "utf-8");
8053
8061
  const peerDeps = JSON.parse(content).peerDependencies ?? {};
8054
- if ("react" in peerDeps && declaredDependencies.get("react") === true) usedPackageNames.add("react");
8055
- if ("react-dom" in peerDeps && declaredDependencies.get("react-dom") === true) usedPackageNames.add("react-dom");
8062
+ if ("react" in peerDeps && declaredDependencies.get("react") === true) markPackageUsed("react");
8063
+ if ("react-dom" in peerDeps && declaredDependencies.get("react-dom") === true) markPackageUsed("react-dom");
8056
8064
  } catch {}
8057
8065
  }
8058
8066
  const peerSatisfied = collectPeerSatisfiedPackages(nodeModulesSearchRoots, declaredNames, usedPackageNames);
8059
8067
  for (const packageName of peerSatisfied) usedPackageNames.add(packageName);
8060
8068
  const overrideMappings = collectOverrideMappings(configSearchRoots, allPackageJsonPaths, monorepoRoot);
8061
8069
  for (const { fromPackage, toPackage } of overrideMappings) {
8062
- if (declaredNames.has(toPackage)) usedPackageNames.add(toPackage);
8063
- if (usedPackageNames.has(fromPackage) && declaredNames.has(toPackage)) usedPackageNames.add(toPackage);
8070
+ if (declaredNames.has(toPackage)) markPackageUsed(toPackage);
8071
+ if (usedPackageNames.has(fromPackage) && declaredNames.has(toPackage)) markPackageUsed(toPackage);
8064
8072
  }
8065
8073
  const candidateUnused = /* @__PURE__ */ new Set();
8074
+ const skippedDependenciesByName = /* @__PURE__ */ new Map();
8075
+ const recordSkippedDependency = (dependencyName, reason) => {
8076
+ const reasons = skippedDependenciesByName.get(dependencyName) ?? /* @__PURE__ */ new Set();
8077
+ reasons.add(reason);
8078
+ skippedDependenciesByName.set(dependencyName, reasons);
8079
+ };
8080
+ for (const [dependencyName] of declaredDependencies) {
8081
+ if (observedPackageNames.has(dependencyName) || peerSatisfied.has(dependencyName)) continue;
8082
+ if (isAlwaysConsideredUsed(dependencyName)) recordSkippedDependency(dependencyName, "allowlisted-name");
8083
+ if (packagesProvidingBinary.has(dependencyName)) recordSkippedDependency(dependencyName, "provides-binary");
8084
+ }
8066
8085
  for (const [dependencyName] of declaredDependencies) {
8067
8086
  if (isAlwaysConsideredUsed(dependencyName)) continue;
8068
8087
  if (usedPackageNames.has(dependencyName)) continue;
@@ -8071,7 +8090,7 @@ const detectStalePackages = (graph, config, summaryCache) => {
8071
8090
  if (candidateUnused.size > 0) {
8072
8091
  const sourceFileRescued = scanSourceFilesForPackageImports(config.rootDir, candidateUnused, summaryCache);
8073
8092
  for (const packageName of sourceFileRescued) {
8074
- usedPackageNames.add(packageName);
8093
+ markPackageUsed(packageName);
8075
8094
  candidateUnused.delete(packageName);
8076
8095
  }
8077
8096
  }
@@ -8085,7 +8104,14 @@ const detectStalePackages = (graph, config, summaryCache) => {
8085
8104
  reason: `"${dependencyName}" is declared in ${dependencySection} but is never imported or referenced by any source file, script, or config — remove it from package.json if it is genuinely unused`
8086
8105
  });
8087
8106
  }
8088
- return unusedDependencies;
8107
+ return {
8108
+ unusedDependencies,
8109
+ skippedDependencies: [...skippedDependenciesByName.entries()].sort(([leftName], [rightName]) => leftName.localeCompare(rightName)).map(([name, reasons]) => ({
8110
+ name,
8111
+ isDevDependency: declaredDependencies.get(name) ?? false,
8112
+ reasons: [...reasons].sort()
8113
+ }))
8114
+ };
8089
8115
  };
8090
8116
  const collectUsedPackages = (graph) => {
8091
8117
  const usedPackages = /* @__PURE__ */ new Set();
@@ -12477,7 +12503,11 @@ const generateReport = (graph, config, summaryCache) => {
12477
12503
  }
12478
12504
  const unusedFiles = safeReportDetector("detectOrphanFiles", () => detectOrphanFiles(graph), [], errorSink);
12479
12505
  const unusedExports = safeReportDetector("detectDeadExports", () => detectDeadExports(graph, config), [], errorSink);
12480
- const unusedDependencies = safeReportDetector("detectStalePackages", () => detectStalePackages(graph, config, summaryCache), [], errorSink);
12506
+ const stalePackageReport = safeReportDetector("detectStalePackages", () => detectStalePackages(graph, config, summaryCache), {
12507
+ unusedDependencies: [],
12508
+ skippedDependencies: []
12509
+ }, errorSink);
12510
+ const unusedDependencies = stalePackageReport.unusedDependencies;
12481
12511
  const circularDependencies = safeReportDetector("detectCycles", () => detectCycles(graph), [], errorSink);
12482
12512
  const syntacticRedundantAliases = config.reportRedundancy ? [...safeReportDetector("detectRedundantAliases", () => detectRedundantAliases(graph), [], errorSink), ...safeReportDetector("detectUselessAliasedReExports", () => detectUselessAliasedReExports(graph), [], errorSink)] : [];
12483
12513
  const duplicateExports = config.reportRedundancy ? safeReportDetector("detectDuplicateExports", () => detectDuplicateExports(graph), [], errorSink) : [];
@@ -12536,6 +12566,7 @@ const generateReport = (graph, config, summaryCache) => {
12536
12566
  unusedFiles,
12537
12567
  unusedExports,
12538
12568
  unusedDependencies,
12569
+ skippedDependencies: stalePackageReport.skippedDependencies,
12539
12570
  circularDependencies,
12540
12571
  unusedTypes: semanticResult.unusedTypes,
12541
12572
  misclassifiedDependencies: semanticResult.misclassifiedDependencies,
package/dist/index.d.cts CHANGED
@@ -49,6 +49,12 @@ interface UnusedDependency {
49
49
  isDevDependency: boolean;
50
50
  reason: string;
51
51
  }
52
+ type SkippedDependencyReason = "allowlisted-name" | "provides-binary";
53
+ interface SkippedDependency {
54
+ name: string;
55
+ isDevDependency: boolean;
56
+ reasons: SkippedDependencyReason[];
57
+ }
52
58
  interface CircularDependency {
53
59
  files: string[];
54
60
  }
@@ -369,6 +375,8 @@ interface ScanResult {
369
375
  unusedFiles: UnusedFile[];
370
376
  unusedExports: UnusedExport[];
371
377
  unusedDependencies: UnusedDependency[];
378
+ /** Declared dependencies conservatively excluded from unused-dependency analysis. */
379
+ skippedDependencies?: SkippedDependency[];
372
380
  circularDependencies: CircularDependency[];
373
381
  unusedTypes: UnusedType[];
374
382
  misclassifiedDependencies: MisclassifiedDependency[];
@@ -465,4 +473,4 @@ declare const defineConfig: (options: Partial<DeslopConfig> & {
465
473
  }) => DeslopConfig;
466
474
  declare const analyze: (config: DeslopConfig) => Promise<ScanResult>;
467
475
  //#endregion
468
- export { type CircularDependency, type ClassMemberKind, type CommonjsInEsm, type CommonjsInEsmKind, type ComplexityConfig, type CrossFileDuplicateExport, type CrossFileDuplicateExportLocation, type DependencyDeclaredAs, type DeslopConfig, type DeslopError, type DeslopErrorCode, type DeslopErrorModule, type DeslopErrorSeverity, type DuplicateBlock, type DuplicateBlockCluster, type DuplicateBlockDetectionMode, type DuplicateBlockOccurrence, type DuplicateBlockRefactoringHint, type DuplicateBlockRefactoringKind, type DuplicateBlocksConfig, type DuplicateConstant, type DuplicateConstantOccurrence, type DuplicateExport, type DuplicateExportOccurrence, type DuplicateImport, type DuplicateImportOccurrence, type DuplicateInlineType, type DuplicateTypeDefinition, type DuplicateTypeDefinitionInstance, type FeatureFlag, type FeatureFlagKind, type FeatureFlagsConfig, type FunctionComplexity, type IdentityWrapper, type InlineTypeContext, type InlineTypeOccurrence, type LazyImportAtTopLevel, type LazyImportKind, type MisclassifiedDependency, type PrivateTypeLeak, type ReExportCycle, type ReExportCycleKind, type RedundantAlias, type RedundantAliasKind, type RedundantTypePattern, type RedundantTypePatternKind, type ScanResult, type SemanticConfidence, type SemanticConfig, type ShadowedDirectoryPair, type SimplifiableExpression, type SimplifiableExpressionKind, type SimplifiableFunction, type SimplifiableFunctionKind, type TypeScriptEscapeHatch, type TypeScriptEscapeHatchKind, type UnnecessaryAssertion, type UnnecessaryAssertionKind, type UnusedClassMember, type UnusedDependency, type UnusedEnumMember, type UnusedExport, type UnusedFile, type UnusedType, type UnusedTypeKind, analyze, defineConfig };
476
+ export { type CircularDependency, type ClassMemberKind, type CommonjsInEsm, type CommonjsInEsmKind, type ComplexityConfig, type CrossFileDuplicateExport, type CrossFileDuplicateExportLocation, type DependencyDeclaredAs, type DeslopConfig, type DeslopError, type DeslopErrorCode, type DeslopErrorModule, type DeslopErrorSeverity, type DuplicateBlock, type DuplicateBlockCluster, type DuplicateBlockDetectionMode, type DuplicateBlockOccurrence, type DuplicateBlockRefactoringHint, type DuplicateBlockRefactoringKind, type DuplicateBlocksConfig, type DuplicateConstant, type DuplicateConstantOccurrence, type DuplicateExport, type DuplicateExportOccurrence, type DuplicateImport, type DuplicateImportOccurrence, type DuplicateInlineType, type DuplicateTypeDefinition, type DuplicateTypeDefinitionInstance, type FeatureFlag, type FeatureFlagKind, type FeatureFlagsConfig, type FunctionComplexity, type IdentityWrapper, type InlineTypeContext, type InlineTypeOccurrence, type LazyImportAtTopLevel, type LazyImportKind, type MisclassifiedDependency, type PrivateTypeLeak, type ReExportCycle, type ReExportCycleKind, type RedundantAlias, type RedundantAliasKind, type RedundantTypePattern, type RedundantTypePatternKind, type ScanResult, type SemanticConfidence, type SemanticConfig, type ShadowedDirectoryPair, type SimplifiableExpression, type SimplifiableExpressionKind, type SimplifiableFunction, type SimplifiableFunctionKind, type SkippedDependency, type SkippedDependencyReason, type TypeScriptEscapeHatch, type TypeScriptEscapeHatchKind, type UnnecessaryAssertion, type UnnecessaryAssertionKind, type UnusedClassMember, type UnusedDependency, type UnusedEnumMember, type UnusedExport, type UnusedFile, type UnusedType, type UnusedTypeKind, analyze, defineConfig };
package/dist/index.d.mts CHANGED
@@ -49,6 +49,12 @@ interface UnusedDependency {
49
49
  isDevDependency: boolean;
50
50
  reason: string;
51
51
  }
52
+ type SkippedDependencyReason = "allowlisted-name" | "provides-binary";
53
+ interface SkippedDependency {
54
+ name: string;
55
+ isDevDependency: boolean;
56
+ reasons: SkippedDependencyReason[];
57
+ }
52
58
  interface CircularDependency {
53
59
  files: string[];
54
60
  }
@@ -369,6 +375,8 @@ interface ScanResult {
369
375
  unusedFiles: UnusedFile[];
370
376
  unusedExports: UnusedExport[];
371
377
  unusedDependencies: UnusedDependency[];
378
+ /** Declared dependencies conservatively excluded from unused-dependency analysis. */
379
+ skippedDependencies?: SkippedDependency[];
372
380
  circularDependencies: CircularDependency[];
373
381
  unusedTypes: UnusedType[];
374
382
  misclassifiedDependencies: MisclassifiedDependency[];
@@ -465,4 +473,4 @@ declare const defineConfig: (options: Partial<DeslopConfig> & {
465
473
  }) => DeslopConfig;
466
474
  declare const analyze: (config: DeslopConfig) => Promise<ScanResult>;
467
475
  //#endregion
468
- export { type CircularDependency, type ClassMemberKind, type CommonjsInEsm, type CommonjsInEsmKind, type ComplexityConfig, type CrossFileDuplicateExport, type CrossFileDuplicateExportLocation, type DependencyDeclaredAs, type DeslopConfig, type DeslopError, type DeslopErrorCode, type DeslopErrorModule, type DeslopErrorSeverity, type DuplicateBlock, type DuplicateBlockCluster, type DuplicateBlockDetectionMode, type DuplicateBlockOccurrence, type DuplicateBlockRefactoringHint, type DuplicateBlockRefactoringKind, type DuplicateBlocksConfig, type DuplicateConstant, type DuplicateConstantOccurrence, type DuplicateExport, type DuplicateExportOccurrence, type DuplicateImport, type DuplicateImportOccurrence, type DuplicateInlineType, type DuplicateTypeDefinition, type DuplicateTypeDefinitionInstance, type FeatureFlag, type FeatureFlagKind, type FeatureFlagsConfig, type FunctionComplexity, type IdentityWrapper, type InlineTypeContext, type InlineTypeOccurrence, type LazyImportAtTopLevel, type LazyImportKind, type MisclassifiedDependency, type PrivateTypeLeak, type ReExportCycle, type ReExportCycleKind, type RedundantAlias, type RedundantAliasKind, type RedundantTypePattern, type RedundantTypePatternKind, type ScanResult, type SemanticConfidence, type SemanticConfig, type ShadowedDirectoryPair, type SimplifiableExpression, type SimplifiableExpressionKind, type SimplifiableFunction, type SimplifiableFunctionKind, type TypeScriptEscapeHatch, type TypeScriptEscapeHatchKind, type UnnecessaryAssertion, type UnnecessaryAssertionKind, type UnusedClassMember, type UnusedDependency, type UnusedEnumMember, type UnusedExport, type UnusedFile, type UnusedType, type UnusedTypeKind, analyze, defineConfig };
476
+ export { type CircularDependency, type ClassMemberKind, type CommonjsInEsm, type CommonjsInEsmKind, type ComplexityConfig, type CrossFileDuplicateExport, type CrossFileDuplicateExportLocation, type DependencyDeclaredAs, type DeslopConfig, type DeslopError, type DeslopErrorCode, type DeslopErrorModule, type DeslopErrorSeverity, type DuplicateBlock, type DuplicateBlockCluster, type DuplicateBlockDetectionMode, type DuplicateBlockOccurrence, type DuplicateBlockRefactoringHint, type DuplicateBlockRefactoringKind, type DuplicateBlocksConfig, type DuplicateConstant, type DuplicateConstantOccurrence, type DuplicateExport, type DuplicateExportOccurrence, type DuplicateImport, type DuplicateImportOccurrence, type DuplicateInlineType, type DuplicateTypeDefinition, type DuplicateTypeDefinitionInstance, type FeatureFlag, type FeatureFlagKind, type FeatureFlagsConfig, type FunctionComplexity, type IdentityWrapper, type InlineTypeContext, type InlineTypeOccurrence, type LazyImportAtTopLevel, type LazyImportKind, type MisclassifiedDependency, type PrivateTypeLeak, type ReExportCycle, type ReExportCycleKind, type RedundantAlias, type RedundantAliasKind, type RedundantTypePattern, type RedundantTypePatternKind, type ScanResult, type SemanticConfidence, type SemanticConfig, type ShadowedDirectoryPair, type SimplifiableExpression, type SimplifiableExpressionKind, type SimplifiableFunction, type SimplifiableFunctionKind, type SkippedDependency, type SkippedDependencyReason, type TypeScriptEscapeHatch, type TypeScriptEscapeHatchKind, type UnnecessaryAssertion, type UnnecessaryAssertionKind, type UnusedClassMember, type UnusedDependency, type UnusedEnumMember, type UnusedExport, type UnusedFile, type UnusedType, type UnusedTypeKind, analyze, defineConfig };
package/dist/index.mjs CHANGED
@@ -7953,7 +7953,10 @@ const detectStalePackages = (graph, config, summaryCache) => {
7953
7953
  const content = readFileSync(packageJsonPath, "utf-8");
7954
7954
  packageJson = JSON.parse(content);
7955
7955
  } catch {
7956
- return [];
7956
+ return {
7957
+ unusedDependencies: [],
7958
+ skippedDependencies: []
7959
+ };
7957
7960
  }
7958
7961
  const dependencies = packageJson.dependencies ?? {};
7959
7962
  const devDependencies = packageJson.devDependencies ?? {};
@@ -7961,7 +7964,12 @@ const detectStalePackages = (graph, config, summaryCache) => {
7961
7964
  for (const dependencyName of Object.keys(dependencies)) declaredDependencies.set(dependencyName, false);
7962
7965
  for (const dependencyName of Object.keys(devDependencies)) declaredDependencies.set(dependencyName, true);
7963
7966
  const declaredNames = new Set(declaredDependencies.keys());
7964
- const usedPackageNames = collectUsedPackages(graph);
7967
+ const observedPackageNames = collectUsedPackages(graph);
7968
+ const usedPackageNames = new Set(observedPackageNames);
7969
+ const markPackageUsed = (packageName) => {
7970
+ observedPackageNames.add(packageName);
7971
+ usedPackageNames.add(packageName);
7972
+ };
7965
7973
  const monorepoRoot = findMonorepoRoot(config.rootDir);
7966
7974
  const nodeModulesSearchRoots = monorepoRoot && monorepoRoot !== config.rootDir ? [config.rootDir, monorepoRoot] : [config.rootDir];
7967
7975
  const allPackageJsonPaths = discoverAllPackageJsonPaths(config.rootDir, summaryCache);
@@ -7973,29 +7981,29 @@ const detectStalePackages = (graph, config, summaryCache) => {
7973
7981
  for (const packageName of packagesProvidingBinary) usedPackageNames.add(packageName);
7974
7982
  for (const workspacePackageJsonPath of allPackageJsonPaths) {
7975
7983
  const scriptReferenced = collectScriptReferencedPackages(workspacePackageJsonPath, declaredNames, binToPackage);
7976
- for (const packageName of scriptReferenced) usedPackageNames.add(packageName);
7984
+ for (const packageName of scriptReferenced) markPackageUsed(packageName);
7977
7985
  const packageJsonConfigReferenced = collectPackageJsonConfigReferences(workspacePackageJsonPath, declaredNames);
7978
- for (const packageName of packageJsonConfigReferenced) usedPackageNames.add(packageName);
7986
+ for (const packageName of packageJsonConfigReferenced) markPackageUsed(packageName);
7979
7987
  }
7980
7988
  const nxProjectReferenced = collectNxProjectJsonReferences(config.rootDir, declaredNames, binToPackage, summaryCache);
7981
- for (const packageName of nxProjectReferenced) usedPackageNames.add(packageName);
7989
+ for (const packageName of nxProjectReferenced) markPackageUsed(packageName);
7982
7990
  const configSearchRoots = monorepoRoot && monorepoRoot !== config.rootDir ? [config.rootDir, monorepoRoot] : [config.rootDir];
7983
7991
  for (const configSearchRoot of configSearchRoots) {
7984
7992
  const configReferenced = collectConfigReferencedPackages(configSearchRoot, graph, declaredNames, summaryCache);
7985
- for (const packageName of configReferenced) usedPackageNames.add(packageName);
7993
+ for (const packageName of configReferenced) markPackageUsed(packageName);
7986
7994
  const tsconfigReferenced = collectTsconfigReferencedPackages(configSearchRoot, summaryCache);
7987
- for (const packageName of tsconfigReferenced) usedPackageNames.add(packageName);
7995
+ for (const packageName of tsconfigReferenced) markPackageUsed(packageName);
7988
7996
  const { packageNames: expoPluginPackageNames } = extractExpoConfigPluginEntries(configSearchRoot, {
7989
7997
  ...dependencies,
7990
7998
  ...devDependencies
7991
7999
  });
7992
- for (const packageName of expoPluginPackageNames) if (declaredNames.has(packageName)) usedPackageNames.add(packageName);
8000
+ for (const packageName of expoPluginPackageNames) if (declaredNames.has(packageName)) markPackageUsed(packageName);
7993
8001
  }
7994
8002
  if (hasJsxFiles(graph)) {
7995
- if (declaredNames.has("react")) usedPackageNames.add("react");
7996
- if (declaredNames.has("react-dom")) usedPackageNames.add("react-dom");
7997
- if (declaredNames.has("react-native")) usedPackageNames.add("react-native");
7998
- if (declaredNames.has("react-native-web")) usedPackageNames.add("react-native-web");
8003
+ if (declaredNames.has("react")) markPackageUsed("react");
8004
+ if (declaredNames.has("react-dom")) markPackageUsed("react-dom");
8005
+ if (declaredNames.has("react-native")) markPackageUsed("react-native");
8006
+ if (declaredNames.has("react-native-web")) markPackageUsed("react-native-web");
7999
8007
  }
8000
8008
  if (declaredNames.has("react-dom")) {
8001
8009
  if ([
@@ -8010,26 +8018,37 @@ const detectStalePackages = (graph, config, summaryCache) => {
8010
8018
  "@tanstack/react-router",
8011
8019
  "@tanstack/react-start",
8012
8020
  "react-app-rewired"
8013
- ].some((framework) => declaredNames.has(framework) || usedPackageNames.has(framework))) usedPackageNames.add("react-dom");
8021
+ ].some((framework) => declaredNames.has(framework) || usedPackageNames.has(framework))) markPackageUsed("react-dom");
8014
8022
  }
8015
- if (declaredNames.has("astro") && declaredNames.has("sharp")) usedPackageNames.add("sharp");
8023
+ if (declaredNames.has("astro") && declaredNames.has("sharp")) markPackageUsed("sharp");
8016
8024
  if (declaredNames.has("react") && declaredNames.has("react-dom")) {
8017
8025
  const packageJsonPath = resolve(config.rootDir, "package.json");
8018
8026
  try {
8019
8027
  const content = readFileSync(packageJsonPath, "utf-8");
8020
8028
  const peerDeps = JSON.parse(content).peerDependencies ?? {};
8021
- if ("react" in peerDeps && declaredDependencies.get("react") === true) usedPackageNames.add("react");
8022
- if ("react-dom" in peerDeps && declaredDependencies.get("react-dom") === true) usedPackageNames.add("react-dom");
8029
+ if ("react" in peerDeps && declaredDependencies.get("react") === true) markPackageUsed("react");
8030
+ if ("react-dom" in peerDeps && declaredDependencies.get("react-dom") === true) markPackageUsed("react-dom");
8023
8031
  } catch {}
8024
8032
  }
8025
8033
  const peerSatisfied = collectPeerSatisfiedPackages(nodeModulesSearchRoots, declaredNames, usedPackageNames);
8026
8034
  for (const packageName of peerSatisfied) usedPackageNames.add(packageName);
8027
8035
  const overrideMappings = collectOverrideMappings(configSearchRoots, allPackageJsonPaths, monorepoRoot);
8028
8036
  for (const { fromPackage, toPackage } of overrideMappings) {
8029
- if (declaredNames.has(toPackage)) usedPackageNames.add(toPackage);
8030
- if (usedPackageNames.has(fromPackage) && declaredNames.has(toPackage)) usedPackageNames.add(toPackage);
8037
+ if (declaredNames.has(toPackage)) markPackageUsed(toPackage);
8038
+ if (usedPackageNames.has(fromPackage) && declaredNames.has(toPackage)) markPackageUsed(toPackage);
8031
8039
  }
8032
8040
  const candidateUnused = /* @__PURE__ */ new Set();
8041
+ const skippedDependenciesByName = /* @__PURE__ */ new Map();
8042
+ const recordSkippedDependency = (dependencyName, reason) => {
8043
+ const reasons = skippedDependenciesByName.get(dependencyName) ?? /* @__PURE__ */ new Set();
8044
+ reasons.add(reason);
8045
+ skippedDependenciesByName.set(dependencyName, reasons);
8046
+ };
8047
+ for (const [dependencyName] of declaredDependencies) {
8048
+ if (observedPackageNames.has(dependencyName) || peerSatisfied.has(dependencyName)) continue;
8049
+ if (isAlwaysConsideredUsed(dependencyName)) recordSkippedDependency(dependencyName, "allowlisted-name");
8050
+ if (packagesProvidingBinary.has(dependencyName)) recordSkippedDependency(dependencyName, "provides-binary");
8051
+ }
8033
8052
  for (const [dependencyName] of declaredDependencies) {
8034
8053
  if (isAlwaysConsideredUsed(dependencyName)) continue;
8035
8054
  if (usedPackageNames.has(dependencyName)) continue;
@@ -8038,7 +8057,7 @@ const detectStalePackages = (graph, config, summaryCache) => {
8038
8057
  if (candidateUnused.size > 0) {
8039
8058
  const sourceFileRescued = scanSourceFilesForPackageImports(config.rootDir, candidateUnused, summaryCache);
8040
8059
  for (const packageName of sourceFileRescued) {
8041
- usedPackageNames.add(packageName);
8060
+ markPackageUsed(packageName);
8042
8061
  candidateUnused.delete(packageName);
8043
8062
  }
8044
8063
  }
@@ -8052,7 +8071,14 @@ const detectStalePackages = (graph, config, summaryCache) => {
8052
8071
  reason: `"${dependencyName}" is declared in ${dependencySection} but is never imported or referenced by any source file, script, or config — remove it from package.json if it is genuinely unused`
8053
8072
  });
8054
8073
  }
8055
- return unusedDependencies;
8074
+ return {
8075
+ unusedDependencies,
8076
+ skippedDependencies: [...skippedDependenciesByName.entries()].sort(([leftName], [rightName]) => leftName.localeCompare(rightName)).map(([name, reasons]) => ({
8077
+ name,
8078
+ isDevDependency: declaredDependencies.get(name) ?? false,
8079
+ reasons: [...reasons].sort()
8080
+ }))
8081
+ };
8056
8082
  };
8057
8083
  const collectUsedPackages = (graph) => {
8058
8084
  const usedPackages = /* @__PURE__ */ new Set();
@@ -12444,7 +12470,11 @@ const generateReport = (graph, config, summaryCache) => {
12444
12470
  }
12445
12471
  const unusedFiles = safeReportDetector("detectOrphanFiles", () => detectOrphanFiles(graph), [], errorSink);
12446
12472
  const unusedExports = safeReportDetector("detectDeadExports", () => detectDeadExports(graph, config), [], errorSink);
12447
- const unusedDependencies = safeReportDetector("detectStalePackages", () => detectStalePackages(graph, config, summaryCache), [], errorSink);
12473
+ const stalePackageReport = safeReportDetector("detectStalePackages", () => detectStalePackages(graph, config, summaryCache), {
12474
+ unusedDependencies: [],
12475
+ skippedDependencies: []
12476
+ }, errorSink);
12477
+ const unusedDependencies = stalePackageReport.unusedDependencies;
12448
12478
  const circularDependencies = safeReportDetector("detectCycles", () => detectCycles(graph), [], errorSink);
12449
12479
  const syntacticRedundantAliases = config.reportRedundancy ? [...safeReportDetector("detectRedundantAliases", () => detectRedundantAliases(graph), [], errorSink), ...safeReportDetector("detectUselessAliasedReExports", () => detectUselessAliasedReExports(graph), [], errorSink)] : [];
12450
12480
  const duplicateExports = config.reportRedundancy ? safeReportDetector("detectDuplicateExports", () => detectDuplicateExports(graph), [], errorSink) : [];
@@ -12503,6 +12533,7 @@ const generateReport = (graph, config, summaryCache) => {
12503
12533
  unusedFiles,
12504
12534
  unusedExports,
12505
12535
  unusedDependencies,
12536
+ skippedDependencies: stalePackageReport.skippedDependencies,
12506
12537
  circularDependencies,
12507
12538
  unusedTypes: semanticResult.unusedTypes,
12508
12539
  misclassifiedDependencies: semanticResult.misclassifiedDependencies,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deslop-js",
3
- "version": "0.9.5",
3
+ "version": "0.9.6",
4
4
  "description": "Remove AI slop from JavaScript code.",
5
5
  "keywords": [
6
6
  "dead-code",