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 +6 -0
- package/dist/index.cjs +52 -21
- package/dist/index.d.cts +9 -1
- package/dist/index.d.mts +9 -1
- package/dist/index.mjs +52 -21
- package/package.json +1 -1
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
|
|
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)
|
|
8017
|
+
for (const packageName of scriptReferenced) markPackageUsed(packageName);
|
|
8010
8018
|
const packageJsonConfigReferenced = collectPackageJsonConfigReferences(workspacePackageJsonPath, declaredNames);
|
|
8011
|
-
for (const packageName of packageJsonConfigReferenced)
|
|
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)
|
|
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)
|
|
8026
|
+
for (const packageName of configReferenced) markPackageUsed(packageName);
|
|
8019
8027
|
const tsconfigReferenced = collectTsconfigReferencedPackages(configSearchRoot, summaryCache);
|
|
8020
|
-
for (const packageName of tsconfigReferenced)
|
|
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))
|
|
8033
|
+
for (const packageName of expoPluginPackageNames) if (declaredNames.has(packageName)) markPackageUsed(packageName);
|
|
8026
8034
|
}
|
|
8027
8035
|
if (hasJsxFiles(graph)) {
|
|
8028
|
-
if (declaredNames.has("react"))
|
|
8029
|
-
if (declaredNames.has("react-dom"))
|
|
8030
|
-
if (declaredNames.has("react-native"))
|
|
8031
|
-
if (declaredNames.has("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)))
|
|
8054
|
+
].some((framework) => declaredNames.has(framework) || usedPackageNames.has(framework))) markPackageUsed("react-dom");
|
|
8047
8055
|
}
|
|
8048
|
-
if (declaredNames.has("astro") && declaredNames.has("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)
|
|
8055
|
-
if ("react-dom" in peerDeps && declaredDependencies.get("react-dom") === true)
|
|
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))
|
|
8063
|
-
if (usedPackageNames.has(fromPackage) && declaredNames.has(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
|
-
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
7984
|
+
for (const packageName of scriptReferenced) markPackageUsed(packageName);
|
|
7977
7985
|
const packageJsonConfigReferenced = collectPackageJsonConfigReferences(workspacePackageJsonPath, declaredNames);
|
|
7978
|
-
for (const packageName of packageJsonConfigReferenced)
|
|
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)
|
|
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)
|
|
7993
|
+
for (const packageName of configReferenced) markPackageUsed(packageName);
|
|
7986
7994
|
const tsconfigReferenced = collectTsconfigReferencedPackages(configSearchRoot, summaryCache);
|
|
7987
|
-
for (const packageName of tsconfigReferenced)
|
|
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))
|
|
8000
|
+
for (const packageName of expoPluginPackageNames) if (declaredNames.has(packageName)) markPackageUsed(packageName);
|
|
7993
8001
|
}
|
|
7994
8002
|
if (hasJsxFiles(graph)) {
|
|
7995
|
-
if (declaredNames.has("react"))
|
|
7996
|
-
if (declaredNames.has("react-dom"))
|
|
7997
|
-
if (declaredNames.has("react-native"))
|
|
7998
|
-
if (declaredNames.has("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)))
|
|
8021
|
+
].some((framework) => declaredNames.has(framework) || usedPackageNames.has(framework))) markPackageUsed("react-dom");
|
|
8014
8022
|
}
|
|
8015
|
-
if (declaredNames.has("astro") && declaredNames.has("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)
|
|
8022
|
-
if ("react-dom" in peerDeps && declaredDependencies.get("react-dom") === true)
|
|
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))
|
|
8030
|
-
if (usedPackageNames.has(fromPackage) && declaredNames.has(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
|
-
|
|
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
|
|
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
|
|
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,
|