codeguardian-mcp 1.0.0
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/LICENSE +21 -0
- package/README.md +348 -0
- package/dist/agent/agentTools.d.ts +26 -0
- package/dist/agent/agentTools.d.ts.map +1 -0
- package/dist/agent/agentTools.js +699 -0
- package/dist/agent/agentTools.js.map +1 -0
- package/dist/agent/autoValidator.d.ts +110 -0
- package/dist/agent/autoValidator.d.ts.map +1 -0
- package/dist/agent/autoValidator.js +964 -0
- package/dist/agent/autoValidator.js.map +1 -0
- package/dist/agent/fileWatcher.d.ts +28 -0
- package/dist/agent/fileWatcher.d.ts.map +1 -0
- package/dist/agent/fileWatcher.js +88 -0
- package/dist/agent/fileWatcher.js.map +1 -0
- package/dist/agent/guardianPersistence.d.ts +98 -0
- package/dist/agent/guardianPersistence.d.ts.map +1 -0
- package/dist/agent/guardianPersistence.js +296 -0
- package/dist/agent/guardianPersistence.js.map +1 -0
- package/dist/agent/mcpNotifications.d.ts +38 -0
- package/dist/agent/mcpNotifications.d.ts.map +1 -0
- package/dist/agent/mcpNotifications.js +81 -0
- package/dist/agent/mcpNotifications.js.map +1 -0
- package/dist/analyzers/aiPatterns.d.ts +16 -0
- package/dist/analyzers/aiPatterns.d.ts.map +1 -0
- package/dist/analyzers/aiPatterns.js +103 -0
- package/dist/analyzers/aiPatterns.js.map +1 -0
- package/dist/analyzers/antiPatterns.d.ts +60 -0
- package/dist/analyzers/antiPatterns.d.ts.map +1 -0
- package/dist/analyzers/antiPatterns.js +198 -0
- package/dist/analyzers/antiPatterns.js.map +1 -0
- package/dist/analyzers/builtinTypes.d.ts +18 -0
- package/dist/analyzers/builtinTypes.d.ts.map +1 -0
- package/dist/analyzers/builtinTypes.js +1275 -0
- package/dist/analyzers/builtinTypes.js.map +1 -0
- package/dist/analyzers/complexity.d.ts +14 -0
- package/dist/analyzers/complexity.d.ts.map +1 -0
- package/dist/analyzers/complexity.js +610 -0
- package/dist/analyzers/complexity.js.map +1 -0
- package/dist/analyzers/findingVerifier.d.ts +59 -0
- package/dist/analyzers/findingVerifier.d.ts.map +1 -0
- package/dist/analyzers/findingVerifier.js +1169 -0
- package/dist/analyzers/findingVerifier.js.map +1 -0
- package/dist/analyzers/impactAnalyzer.d.ts +53 -0
- package/dist/analyzers/impactAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/impactAnalyzer.js +152 -0
- package/dist/analyzers/impactAnalyzer.js.map +1 -0
- package/dist/analyzers/languageDetector.d.ts +48 -0
- package/dist/analyzers/languageDetector.d.ts.map +1 -0
- package/dist/analyzers/languageDetector.js +404 -0
- package/dist/analyzers/languageDetector.js.map +1 -0
- package/dist/analyzers/parsers/incrementalParser.d.ts +53 -0
- package/dist/analyzers/parsers/incrementalParser.d.ts.map +1 -0
- package/dist/analyzers/parsers/incrementalParser.js +193 -0
- package/dist/analyzers/parsers/incrementalParser.js.map +1 -0
- package/dist/analyzers/parsers/scopeResolver.d.ts +92 -0
- package/dist/analyzers/parsers/scopeResolver.d.ts.map +1 -0
- package/dist/analyzers/parsers/scopeResolver.js +324 -0
- package/dist/analyzers/parsers/scopeResolver.js.map +1 -0
- package/dist/analyzers/parsers/semanticIndex.d.ts +127 -0
- package/dist/analyzers/parsers/semanticIndex.d.ts.map +1 -0
- package/dist/analyzers/parsers/semanticIndex.js +429 -0
- package/dist/analyzers/parsers/semanticIndex.js.map +1 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts +42 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.js +233 -0
- package/dist/analyzers/parsers/sessionDiffAnalyzer.js.map +1 -0
- package/dist/analyzers/parsers/treeSitterParser.d.ts +76 -0
- package/dist/analyzers/parsers/treeSitterParser.d.ts.map +1 -0
- package/dist/analyzers/parsers/treeSitterParser.js +709 -0
- package/dist/analyzers/parsers/treeSitterParser.js.map +1 -0
- package/dist/analyzers/relevanceScorer.d.ts +43 -0
- package/dist/analyzers/relevanceScorer.d.ts.map +1 -0
- package/dist/analyzers/relevanceScorer.js +200 -0
- package/dist/analyzers/relevanceScorer.js.map +1 -0
- package/dist/analyzers/standardLibrary.d.ts +22 -0
- package/dist/analyzers/standardLibrary.d.ts.map +1 -0
- package/dist/analyzers/standardLibrary.js +211 -0
- package/dist/analyzers/standardLibrary.js.map +1 -0
- package/dist/analyzers/symbolGraph.d.ts +30 -0
- package/dist/analyzers/symbolGraph.d.ts.map +1 -0
- package/dist/analyzers/symbolGraph.js +380 -0
- package/dist/analyzers/symbolGraph.js.map +1 -0
- package/dist/analyzers/symbolTable.d.ts +18 -0
- package/dist/analyzers/symbolTable.d.ts.map +1 -0
- package/dist/analyzers/symbolTable.js +176 -0
- package/dist/analyzers/symbolTable.js.map +1 -0
- package/dist/analyzers/typeChecker.d.ts +13 -0
- package/dist/analyzers/typeChecker.d.ts.map +1 -0
- package/dist/analyzers/typeChecker.js +580 -0
- package/dist/analyzers/typeChecker.js.map +1 -0
- package/dist/analyzers/usagePatterns.d.ts +42 -0
- package/dist/analyzers/usagePatterns.d.ts.map +1 -0
- package/dist/analyzers/usagePatterns.js +75 -0
- package/dist/analyzers/usagePatterns.js.map +1 -0
- package/dist/api-contract/context/backend.d.ts +19 -0
- package/dist/api-contract/context/backend.d.ts.map +1 -0
- package/dist/api-contract/context/backend.js +64 -0
- package/dist/api-contract/context/backend.js.map +1 -0
- package/dist/api-contract/context/contract.d.ts +34 -0
- package/dist/api-contract/context/contract.d.ts.map +1 -0
- package/dist/api-contract/context/contract.js +306 -0
- package/dist/api-contract/context/contract.js.map +1 -0
- package/dist/api-contract/context/frontend.d.ts +19 -0
- package/dist/api-contract/context/frontend.d.ts.map +1 -0
- package/dist/api-contract/context/frontend.js +64 -0
- package/dist/api-contract/context/frontend.js.map +1 -0
- package/dist/api-contract/detector.d.ts +28 -0
- package/dist/api-contract/detector.d.ts.map +1 -0
- package/dist/api-contract/detector.js +393 -0
- package/dist/api-contract/detector.js.map +1 -0
- package/dist/api-contract/extractors/python.d.ts +32 -0
- package/dist/api-contract/extractors/python.d.ts.map +1 -0
- package/dist/api-contract/extractors/python.js +521 -0
- package/dist/api-contract/extractors/python.js.map +1 -0
- package/dist/api-contract/extractors/pythonAstUtils.d.ts +44 -0
- package/dist/api-contract/extractors/pythonAstUtils.d.ts.map +1 -0
- package/dist/api-contract/extractors/pythonAstUtils.js +489 -0
- package/dist/api-contract/extractors/pythonAstUtils.js.map +1 -0
- package/dist/api-contract/extractors/tsAstUtils.d.ts +47 -0
- package/dist/api-contract/extractors/tsAstUtils.d.ts.map +1 -0
- package/dist/api-contract/extractors/tsAstUtils.js +173 -0
- package/dist/api-contract/extractors/tsAstUtils.js.map +1 -0
- package/dist/api-contract/extractors/typescript.d.ts +32 -0
- package/dist/api-contract/extractors/typescript.d.ts.map +1 -0
- package/dist/api-contract/extractors/typescript.js +666 -0
- package/dist/api-contract/extractors/typescript.js.map +1 -0
- package/dist/api-contract/index.d.ts +104 -0
- package/dist/api-contract/index.d.ts.map +1 -0
- package/dist/api-contract/index.js +232 -0
- package/dist/api-contract/index.js.map +1 -0
- package/dist/api-contract/types.d.ts +151 -0
- package/dist/api-contract/types.d.ts.map +1 -0
- package/dist/api-contract/types.js +19 -0
- package/dist/api-contract/types.js.map +1 -0
- package/dist/api-contract/validators/endpoint.d.ts +21 -0
- package/dist/api-contract/validators/endpoint.d.ts.map +1 -0
- package/dist/api-contract/validators/endpoint.js +224 -0
- package/dist/api-contract/validators/endpoint.js.map +1 -0
- package/dist/api-contract/validators/index.d.ts +40 -0
- package/dist/api-contract/validators/index.d.ts.map +1 -0
- package/dist/api-contract/validators/index.js +875 -0
- package/dist/api-contract/validators/index.js.map +1 -0
- package/dist/api-contract/validators/parameter.d.ts +17 -0
- package/dist/api-contract/validators/parameter.d.ts.map +1 -0
- package/dist/api-contract/validators/parameter.js +250 -0
- package/dist/api-contract/validators/parameter.js.map +1 -0
- package/dist/api-contract/validators/type.d.ts +38 -0
- package/dist/api-contract/validators/type.d.ts.map +1 -0
- package/dist/api-contract/validators/type.js +244 -0
- package/dist/api-contract/validators/type.js.map +1 -0
- package/dist/context/apiContract/complexTypeSupport.d.ts +83 -0
- package/dist/context/apiContract/complexTypeSupport.d.ts.map +1 -0
- package/dist/context/apiContract/complexTypeSupport.js +665 -0
- package/dist/context/apiContract/complexTypeSupport.js.map +1 -0
- package/dist/context/apiContract/graphqlSupport.d.ts +105 -0
- package/dist/context/apiContract/graphqlSupport.d.ts.map +1 -0
- package/dist/context/apiContract/graphqlSupport.js +671 -0
- package/dist/context/apiContract/graphqlSupport.js.map +1 -0
- package/dist/context/apiContract/index.d.ts +14 -0
- package/dist/context/apiContract/index.d.ts.map +1 -0
- package/dist/context/apiContract/index.js +17 -0
- package/dist/context/apiContract/index.js.map +1 -0
- package/dist/context/apiContract/webSocketSupport.d.ts +104 -0
- package/dist/context/apiContract/webSocketSupport.d.ts.map +1 -0
- package/dist/context/apiContract/webSocketSupport.js +465 -0
- package/dist/context/apiContract/webSocketSupport.js.map +1 -0
- package/dist/context/apiContractContext.d.ts +15 -0
- package/dist/context/apiContractContext.d.ts.map +1 -0
- package/dist/context/apiContractContext.js +979 -0
- package/dist/context/apiContractContext.js.map +1 -0
- package/dist/context/apiContractExtraction.d.ts +52 -0
- package/dist/context/apiContractExtraction.d.ts.map +1 -0
- package/dist/context/apiContractExtraction.js +438 -0
- package/dist/context/apiContractExtraction.js.map +1 -0
- package/dist/context/contextLineage.d.ts +79 -0
- package/dist/context/contextLineage.d.ts.map +1 -0
- package/dist/context/contextLineage.js +259 -0
- package/dist/context/contextLineage.js.map +1 -0
- package/dist/context/contextOrchestrator.d.ts +57 -0
- package/dist/context/contextOrchestrator.d.ts.map +1 -0
- package/dist/context/contextOrchestrator.js +162 -0
- package/dist/context/contextOrchestrator.js.map +1 -0
- package/dist/context/intentTracker.d.ts +73 -0
- package/dist/context/intentTracker.d.ts.map +1 -0
- package/dist/context/intentTracker.js +168 -0
- package/dist/context/intentTracker.js.map +1 -0
- package/dist/context/projectContext.d.ts +219 -0
- package/dist/context/projectContext.d.ts.map +1 -0
- package/dist/context/projectContext.js +1984 -0
- package/dist/context/projectContext.js.map +1 -0
- package/dist/prompts/index.d.ts +17 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +260 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/library.d.ts +51 -0
- package/dist/prompts/library.d.ts.map +1 -0
- package/dist/prompts/library.js +65 -0
- package/dist/prompts/library.js.map +1 -0
- package/dist/prompts/templates.d.ts +44 -0
- package/dist/prompts/templates.d.ts.map +1 -0
- package/dist/prompts/templates.js +97 -0
- package/dist/prompts/templates.js.map +1 -0
- package/dist/queue/jobPersistence.d.ts +46 -0
- package/dist/queue/jobPersistence.d.ts.map +1 -0
- package/dist/queue/jobPersistence.js +158 -0
- package/dist/queue/jobPersistence.js.map +1 -0
- package/dist/queue/jobQueue.d.ts +116 -0
- package/dist/queue/jobQueue.d.ts.map +1 -0
- package/dist/queue/jobQueue.js +275 -0
- package/dist/queue/jobQueue.js.map +1 -0
- package/dist/queue/validationJob.d.ts +69 -0
- package/dist/queue/validationJob.d.ts.map +1 -0
- package/dist/queue/validationJob.js +435 -0
- package/dist/queue/validationJob.js.map +1 -0
- package/dist/resources/index.d.ts +15 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +328 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/validationReportStore.d.ts +170 -0
- package/dist/resources/validationReportStore.d.ts.map +1 -0
- package/dist/resources/validationReportStore.js +515 -0
- package/dist/resources/validationReportStore.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +102 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/asyncValidation.d.ts +19 -0
- package/dist/tools/asyncValidation.d.ts.map +1 -0
- package/dist/tools/asyncValidation.js +346 -0
- package/dist/tools/asyncValidation.js.map +1 -0
- package/dist/tools/buildContext.d.ts +17 -0
- package/dist/tools/buildContext.d.ts.map +1 -0
- package/dist/tools/buildContext.js +188 -0
- package/dist/tools/buildContext.js.map +1 -0
- package/dist/tools/getDependencyGraph.d.ts +16 -0
- package/dist/tools/getDependencyGraph.d.ts.map +1 -0
- package/dist/tools/getDependencyGraph.js +436 -0
- package/dist/tools/getDependencyGraph.js.map +1 -0
- package/dist/tools/incrementalValidation.d.ts +71 -0
- package/dist/tools/incrementalValidation.d.ts.map +1 -0
- package/dist/tools/incrementalValidation.js +203 -0
- package/dist/tools/incrementalValidation.js.map +1 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +106 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/validateCode.d.ts +17 -0
- package/dist/tools/validateCode.d.ts.map +1 -0
- package/dist/tools/validateCode.js +368 -0
- package/dist/tools/validateCode.js.map +1 -0
- package/dist/tools/validateCodeLite.d.ts +2 -0
- package/dist/tools/validateCodeLite.d.ts.map +1 -0
- package/dist/tools/validateCodeLite.js +2 -0
- package/dist/tools/validateCodeLite.js.map +1 -0
- package/dist/tools/validation/builtins.d.ts +92 -0
- package/dist/tools/validation/builtins.d.ts.map +1 -0
- package/dist/tools/validation/builtins.js +2184 -0
- package/dist/tools/validation/builtins.js.map +1 -0
- package/dist/tools/validation/contextualNaming.d.ts +99 -0
- package/dist/tools/validation/contextualNaming.d.ts.map +1 -0
- package/dist/tools/validation/contextualNaming.js +959 -0
- package/dist/tools/validation/contextualNaming.js.map +1 -0
- package/dist/tools/validation/deadCode.d.ts +115 -0
- package/dist/tools/validation/deadCode.d.ts.map +1 -0
- package/dist/tools/validation/deadCode.js +861 -0
- package/dist/tools/validation/deadCode.js.map +1 -0
- package/dist/tools/validation/extractors/index.d.ts +131 -0
- package/dist/tools/validation/extractors/index.d.ts.map +1 -0
- package/dist/tools/validation/extractors/index.js +233 -0
- package/dist/tools/validation/extractors/index.js.map +1 -0
- package/dist/tools/validation/extractors/javascript.d.ts +73 -0
- package/dist/tools/validation/extractors/javascript.d.ts.map +1 -0
- package/dist/tools/validation/extractors/javascript.js +1841 -0
- package/dist/tools/validation/extractors/javascript.js.map +1 -0
- package/dist/tools/validation/extractors/python.d.ts +93 -0
- package/dist/tools/validation/extractors/python.d.ts.map +1 -0
- package/dist/tools/validation/extractors/python.js +799 -0
- package/dist/tools/validation/extractors/python.js.map +1 -0
- package/dist/tools/validation/manifest.d.ts +45 -0
- package/dist/tools/validation/manifest.d.ts.map +1 -0
- package/dist/tools/validation/manifest.js +719 -0
- package/dist/tools/validation/manifest.js.map +1 -0
- package/dist/tools/validation/parser.d.ts +58 -0
- package/dist/tools/validation/parser.d.ts.map +1 -0
- package/dist/tools/validation/parser.js +232 -0
- package/dist/tools/validation/parser.js.map +1 -0
- package/dist/tools/validation/registry.d.ts +15 -0
- package/dist/tools/validation/registry.d.ts.map +1 -0
- package/dist/tools/validation/registry.js +169 -0
- package/dist/tools/validation/registry.js.map +1 -0
- package/dist/tools/validation/scoring.d.ts +54 -0
- package/dist/tools/validation/scoring.d.ts.map +1 -0
- package/dist/tools/validation/scoring.js +242 -0
- package/dist/tools/validation/scoring.js.map +1 -0
- package/dist/tools/validation/types.d.ts +120 -0
- package/dist/tools/validation/types.d.ts.map +1 -0
- package/dist/tools/validation/types.js +11 -0
- package/dist/tools/validation/types.js.map +1 -0
- package/dist/tools/validation/unusedLocals.d.ts +36 -0
- package/dist/tools/validation/unusedLocals.d.ts.map +1 -0
- package/dist/tools/validation/unusedLocals.js +333 -0
- package/dist/tools/validation/unusedLocals.js.map +1 -0
- package/dist/tools/validation/validation.d.ts +98 -0
- package/dist/tools/validation/validation.d.ts.map +1 -0
- package/dist/tools/validation/validation.js +1837 -0
- package/dist/tools/validation/validation.js.map +1 -0
- package/dist/types/codeGraph.d.ts +163 -0
- package/dist/types/codeGraph.d.ts.map +1 -0
- package/dist/types/codeGraph.js +9 -0
- package/dist/types/codeGraph.js.map +1 -0
- package/dist/types/symbolGraph.d.ts +68 -0
- package/dist/types/symbolGraph.d.ts.map +1 -0
- package/dist/types/symbolGraph.js +10 -0
- package/dist/types/symbolGraph.js.map +1 -0
- package/dist/types/tools.d.ts +43 -0
- package/dist/types/tools.d.ts.map +1 -0
- package/dist/types/tools.js +7 -0
- package/dist/types/tools.js.map +1 -0
- package/dist/utils/fileFilter.d.ts +37 -0
- package/dist/utils/fileFilter.d.ts.map +1 -0
- package/dist/utils/fileFilter.js +91 -0
- package/dist/utils/fileFilter.js.map +1 -0
- package/dist/utils/gitUtils.d.ts +28 -0
- package/dist/utils/gitUtils.d.ts.map +1 -0
- package/dist/utils/gitUtils.js +81 -0
- package/dist/utils/gitUtils.js.map +1 -0
- package/dist/utils/logger.d.ts +15 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +38 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/serialization.d.ts +25 -0
- package/dist/utils/serialization.d.ts.map +1 -0
- package/dist/utils/serialization.js +53 -0
- package/dist/utils/serialization.js.map +1 -0
- package/package.json +90 -0
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead Code Analyzer Module
|
|
3
|
+
*
|
|
4
|
+
* This module detects unused exports, orphaned files, and unused functions in a codebase.
|
|
5
|
+
* It uses a multi-phase analysis approach:
|
|
6
|
+
* 1. Fast import graph check (O(1) lookup)
|
|
7
|
+
* 2. Selective deep AST analysis for potentially unused symbols
|
|
8
|
+
* 3. Timeout protection and batch processing for large codebases
|
|
9
|
+
*
|
|
10
|
+
* The analyzer maintains several caches for performance:
|
|
11
|
+
* - fileContentCache: Avoids re-reading files
|
|
12
|
+
* - typeReferencesCache: Avoids re-parsing AST for type references
|
|
13
|
+
* - symbolUsageCache: Avoids redundant symbol usage checks
|
|
14
|
+
*
|
|
15
|
+
* @format
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from "fs/promises";
|
|
18
|
+
import { resolveImport } from "../../context/projectContext.js";
|
|
19
|
+
import { extractImportsAST, extractUsagesAST, } from "./extractors/index.js";
|
|
20
|
+
import { logger } from "../../utils/logger.js";
|
|
21
|
+
import * as path from "path";
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
import { minimatch as minimatchFunc } from "minimatch";
|
|
24
|
+
const minimatch = minimatchFunc;
|
|
25
|
+
// Import the AST-based unused locals detection
|
|
26
|
+
import { detectUnusedLocals, detectUnusedLocalsAST } from "./unusedLocals.js";
|
|
27
|
+
export { detectUnusedLocals, detectUnusedLocalsAST };
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Caches for Performance
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// Cache for file contents to avoid re-reading
|
|
32
|
+
const fileContentCache = new Map();
|
|
33
|
+
// Cache for AST type references to avoid re-parsing
|
|
34
|
+
const typeReferencesCache = new Map();
|
|
35
|
+
// Cache for symbol usage results to avoid redundant checks
|
|
36
|
+
const symbolUsageCache = new Map();
|
|
37
|
+
// Cache for file modification times to enable incremental analysis
|
|
38
|
+
const fileMtimeCache = new Map();
|
|
39
|
+
// Cache for string literals per file
|
|
40
|
+
const fileStringsCache = new Map();
|
|
41
|
+
/**
|
|
42
|
+
* Check if a file has been modified since last scan
|
|
43
|
+
*/
|
|
44
|
+
async function hasFileChanged(filePath) {
|
|
45
|
+
try {
|
|
46
|
+
const stats = await fs.stat(filePath);
|
|
47
|
+
const currentMtime = stats.mtimeMs;
|
|
48
|
+
const cachedMtime = fileMtimeCache.get(filePath);
|
|
49
|
+
if (cachedMtime === undefined || cachedMtime !== currentMtime) {
|
|
50
|
+
// File is new or has been modified
|
|
51
|
+
fileMtimeCache.set(filePath, currentMtime);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
// If we can't stat the file, assume it changed
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Helper Functions for Dead Code Detection
|
|
63
|
+
// ============================================================================
|
|
64
|
+
/**
|
|
65
|
+
* Check if a symbol is directly imported anywhere in the codebase.
|
|
66
|
+
*
|
|
67
|
+
* This is the fastest check - O(1) lookup in the import graph.
|
|
68
|
+
* If a symbol is imported, it's definitely being used.
|
|
69
|
+
*
|
|
70
|
+
* @param symbolName - Name of the symbol to check
|
|
71
|
+
* @param definedInFile - File path where the symbol is defined
|
|
72
|
+
* @param symbolsImportedFromFile - Map of file paths to sets of imported symbol names
|
|
73
|
+
* @returns true if the symbol is imported anywhere, false otherwise
|
|
74
|
+
*/
|
|
75
|
+
export function isSymbolImported(symbolName, definedInFile, symbolsImportedFromFile) {
|
|
76
|
+
const importedSymbols = symbolsImportedFromFile.get(definedInFile);
|
|
77
|
+
return importedSymbols?.has(symbolName) ?? false;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Check if a symbol is referenced as a type anywhere in the codebase.
|
|
81
|
+
*
|
|
82
|
+
* Uses AST-based type reference extraction with pre-loaded cache.
|
|
83
|
+
* This is more expensive than import checking, so we limit the scope.
|
|
84
|
+
*
|
|
85
|
+
* Type references include:
|
|
86
|
+
* - Type annotations (: TypeName)
|
|
87
|
+
* - Generic parameters (<TypeName>)
|
|
88
|
+
* - Return types
|
|
89
|
+
* - Extends/implements clauses
|
|
90
|
+
* - Property types in interfaces
|
|
91
|
+
*
|
|
92
|
+
* @param symbolName - Name of the symbol to check
|
|
93
|
+
* @param definedInFile - File path where the symbol is defined
|
|
94
|
+
* @param context - Project context with file information
|
|
95
|
+
* @returns true if the symbol is referenced as a type anywhere, false otherwise
|
|
96
|
+
*/
|
|
97
|
+
export async function isSymbolTypeReferenced(symbolName, definedInFile, context) {
|
|
98
|
+
// OPTIMIZED: Use symbolGraph for fast lookup (O(1))
|
|
99
|
+
if (context.symbolGraph) {
|
|
100
|
+
const usage = context.symbolGraph.usage.get(symbolName);
|
|
101
|
+
if (usage && usage.calledBy.size > 0) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Check if any file imports this symbol as a type
|
|
106
|
+
// This is faster than parsing AST for type references
|
|
107
|
+
for (const [filePath, fileInfo] of context.files) {
|
|
108
|
+
for (const imp of fileInfo.imports) {
|
|
109
|
+
if (imp.namedImports.includes(symbolName) || imp.defaultImport === symbolName) {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Check if a symbol is used in function calls or method calls via AST.
|
|
118
|
+
*
|
|
119
|
+
* This checks for runtime usage of symbols (not just type references).
|
|
120
|
+
* Uses cached file contents and limits scope for performance.
|
|
121
|
+
*
|
|
122
|
+
* @param symbolName - Name of the symbol to check
|
|
123
|
+
* @param context - Project context with file information
|
|
124
|
+
* @returns true if the symbol is called or instantiated anywhere, false otherwise
|
|
125
|
+
*/
|
|
126
|
+
export async function isSymbolCalledOrInstantiated(symbolName, context, symbolScope) {
|
|
127
|
+
// Signal 1: Check the pre-computed symbol graph (Secret #3)
|
|
128
|
+
// This is extremely fast (O(1)) and accurate if the graph is AST-driven
|
|
129
|
+
if (context.symbolGraph) {
|
|
130
|
+
const usage = context.symbolGraph.usage.get(symbolName);
|
|
131
|
+
// Note: calledBy in symbolGraph includes files that call it
|
|
132
|
+
if (usage && usage.calledBy.size > 0) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Fallback: Selective file content check with regex pre-filter
|
|
137
|
+
// Instead of parsing full AST for up to 50 files per symbol (very expensive),
|
|
138
|
+
// use a fast regex pre-filter on cached file content to find candidate files,
|
|
139
|
+
// then only do AST analysis on files that actually contain the symbol name.
|
|
140
|
+
let checkedFiles = 0;
|
|
141
|
+
const MAX_FILES_TO_CHECK = 50; // Limit scope to prevent timeout
|
|
142
|
+
const symbolRegex = new RegExp(`\\b${escapeRegex(symbolName)}\\b`);
|
|
143
|
+
for (const [filePath, fileInfo] of context.files) {
|
|
144
|
+
if (checkedFiles >= MAX_FILES_TO_CHECK)
|
|
145
|
+
break;
|
|
146
|
+
// Get content from cache or read from disk
|
|
147
|
+
let content = fileContentCache.get(filePath);
|
|
148
|
+
if (!content) {
|
|
149
|
+
try {
|
|
150
|
+
content = await fs.readFile(filePath, "utf-8");
|
|
151
|
+
fileContentCache.set(filePath, content);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Skip files we can't read
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
checkedFiles++;
|
|
159
|
+
// Fast pre-filter: skip files that don't even contain the symbol name
|
|
160
|
+
if (!symbolRegex.test(content))
|
|
161
|
+
continue;
|
|
162
|
+
// Only parse AST for files that actually reference the symbol
|
|
163
|
+
const lang = fileInfo.language === "javascript" ? "javascript" : "typescript";
|
|
164
|
+
const imports = extractImportsAST(content, lang);
|
|
165
|
+
const usages = extractUsagesAST(content, lang, imports);
|
|
166
|
+
// Check for direct function calls or instantiations
|
|
167
|
+
for (const usage of usages) {
|
|
168
|
+
if (usage.name === symbolName) {
|
|
169
|
+
// If symbol has a scope (e.g., it's a method of an object),
|
|
170
|
+
// verify that the usage is via that object
|
|
171
|
+
if (symbolScope && usage.type === "methodCall" && usage.object) {
|
|
172
|
+
if (usage.object === symbolScope) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
// Check if the object is imported from the file where symbol is defined
|
|
176
|
+
const fi = context.files.get(filePath);
|
|
177
|
+
if (fi) {
|
|
178
|
+
const allFileKeys = Array.from(context.files.keys());
|
|
179
|
+
for (const imp of fi.imports) {
|
|
180
|
+
const resolvedPath = resolveImport(imp.source, filePath, allFileKeys);
|
|
181
|
+
if (resolvedPath) {
|
|
182
|
+
const defFileInfo = context.files.get(resolvedPath);
|
|
183
|
+
if (defFileInfo) {
|
|
184
|
+
const matchingSymbol = defFileInfo.symbols.find(s => s.name === symbolName && s.scope === symbolScope && s.exported);
|
|
185
|
+
if (matchingSymbol) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else if (!symbolScope) {
|
|
194
|
+
// No scope required, any usage matches
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Check for method calls on objects (e.g., api.symbolName())
|
|
199
|
+
if (usage.type === "methodCall" && usage.name === symbolName && !symbolScope) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Check if a symbol is used within other exported symbols in the same file.
|
|
208
|
+
*
|
|
209
|
+
* This handles cases like:
|
|
210
|
+
* - ChatResponse used as return type in sendMessage()
|
|
211
|
+
* - ActiveDeliverable used in ActiveProject interface
|
|
212
|
+
* - PYTHON_BUILTINS used in isStandardLibrary() via PYTHON_BUILTINS.has()
|
|
213
|
+
* - Methods within exported service objects (e.g., supportChatService.startNewConversation)
|
|
214
|
+
*
|
|
215
|
+
* If a type/constant is used by an exported function/class, it should not be flagged as unused.
|
|
216
|
+
*
|
|
217
|
+
* @param symbolName - Name of the symbol to check
|
|
218
|
+
* @param definedInFile - File path where the symbol is defined
|
|
219
|
+
* @param context - Project context with file information
|
|
220
|
+
* @param symbolsImportedFromFile - Map of file paths to sets of imported symbol names
|
|
221
|
+
* @returns true if the symbol is used in same-file exports, false otherwise
|
|
222
|
+
*/
|
|
223
|
+
export async function isSymbolUsedInSameFileExports(symbolName, definedInFile, context, _symbolsImportedFromFile) {
|
|
224
|
+
const fileInfo = context.files.get(definedInFile);
|
|
225
|
+
if (!fileInfo)
|
|
226
|
+
return false;
|
|
227
|
+
// Check if this symbol is a method/property of an exported parent object
|
|
228
|
+
// e.g., startNewConversation is a method of supportChatService which is exported
|
|
229
|
+
const symbol = fileInfo.symbols.find(s => s.name === symbolName);
|
|
230
|
+
if (symbol?.scope) {
|
|
231
|
+
// This symbol has a scope (parent object) - check if the parent is exported
|
|
232
|
+
const parentSymbol = fileInfo.symbols.find(s => s.name === symbol.scope);
|
|
233
|
+
if (parentSymbol?.exported) {
|
|
234
|
+
// The parent object is exported, so this method is accessible via the parent
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// OPTIMIZED: Check if symbol is used by other exports in the same file
|
|
239
|
+
// by examining the import/export relationships in the context
|
|
240
|
+
// Check if any other symbol in the same file references this symbol
|
|
241
|
+
for (const sym of fileInfo.symbols) {
|
|
242
|
+
if (sym.name === symbolName)
|
|
243
|
+
continue;
|
|
244
|
+
// If another exported symbol exists, check if this symbol is used in its context
|
|
245
|
+
if (sym.exported && sym.returnType?.includes(symbolName)) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Check for member access patterns using cached file content
|
|
250
|
+
try {
|
|
251
|
+
let content = fileContentCache.get(definedInFile);
|
|
252
|
+
if (!content) {
|
|
253
|
+
content = await fs.readFile(definedInFile, "utf-8");
|
|
254
|
+
fileContentCache.set(definedInFile, content);
|
|
255
|
+
}
|
|
256
|
+
// Pattern 1: symbolName followed by a dot (member access via dot notation)
|
|
257
|
+
// Pattern 2: symbolName followed by [ (member access via bracket notation)
|
|
258
|
+
const memberAccessPattern = new RegExp(`\\b${escapeRegex(symbolName)}\\s*[.\\[]`, "g");
|
|
259
|
+
// Count occurrences, excluding the definition line itself
|
|
260
|
+
const matches = content.match(memberAccessPattern);
|
|
261
|
+
if (matches && matches.length > 0) {
|
|
262
|
+
// Found member access - the symbol is being used
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// File read failed, continue with other checks
|
|
268
|
+
}
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Escape special regex characters in a string
|
|
273
|
+
*/
|
|
274
|
+
function escapeRegex(str) {
|
|
275
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Check if a symbol is used anywhere in the codebase using AST analysis.
|
|
279
|
+
*
|
|
280
|
+
* This is purely AST-based - no regex patterns. It checks for:
|
|
281
|
+
* - Direct imports of the symbol
|
|
282
|
+
* - Type annotations (: TypeName) via AST
|
|
283
|
+
* - Generic parameters (<TypeName>) via AST
|
|
284
|
+
* - Return types via AST
|
|
285
|
+
* - Extends/implements clauses via AST
|
|
286
|
+
* - Property types in interfaces via AST
|
|
287
|
+
* - Function/method call usage via AST
|
|
288
|
+
*
|
|
289
|
+
* Uses caching to avoid redundant checks.
|
|
290
|
+
*
|
|
291
|
+
* @param symbolName - Name of the symbol to check
|
|
292
|
+
* @param definedInFile - File path where the symbol is defined
|
|
293
|
+
* @param context - Project context with file information
|
|
294
|
+
* @param symbolsImportedFromFile - Map of file paths to sets of imported symbol names
|
|
295
|
+
* @returns true if the symbol is used anywhere, false otherwise
|
|
296
|
+
*/
|
|
297
|
+
async function isSymbolUsedAnywhere(symbolName, definedInFile, context, symbolsImportedFromFile) {
|
|
298
|
+
// Check cache first
|
|
299
|
+
const cacheKey = `${definedInFile}:${symbolName}`;
|
|
300
|
+
const cached = symbolUsageCache.get(cacheKey);
|
|
301
|
+
if (cached !== undefined) {
|
|
302
|
+
return cached;
|
|
303
|
+
}
|
|
304
|
+
// Check 1: Is it directly imported anywhere? (fastest check)
|
|
305
|
+
if (isSymbolImported(symbolName, definedInFile, symbolsImportedFromFile)) {
|
|
306
|
+
symbolUsageCache.set(cacheKey, true);
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
// Check 2: Is it referenced as a type anywhere?
|
|
310
|
+
if (await isSymbolTypeReferenced(symbolName, definedInFile, context)) {
|
|
311
|
+
symbolUsageCache.set(cacheKey, true);
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
// Check 3: Is it called or instantiated anywhere?
|
|
315
|
+
// Look up the symbol to get its scope (for method detection)
|
|
316
|
+
const fileInfo = context.files.get(definedInFile);
|
|
317
|
+
const symbol = fileInfo?.symbols.find(s => s.name === symbolName);
|
|
318
|
+
if (await isSymbolCalledOrInstantiated(symbolName, context, symbol?.scope)) {
|
|
319
|
+
symbolUsageCache.set(cacheKey, true);
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
// Check 4: Is it used within other exported symbols in the same file?
|
|
323
|
+
if (await isSymbolUsedInSameFileExports(symbolName, definedInFile, context, symbolsImportedFromFile)) {
|
|
324
|
+
symbolUsageCache.set(cacheKey, true);
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
symbolUsageCache.set(cacheKey, false);
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
// ============================================================================
|
|
331
|
+
// Main Dead Code Detection Function
|
|
332
|
+
// ============================================================================
|
|
333
|
+
/**
|
|
334
|
+
* Detect unused exports, orphaned files, and unused functions in a codebase.
|
|
335
|
+
*
|
|
336
|
+
* Uses a multi-phase analysis approach:
|
|
337
|
+
* 1. Build import graph and JSX component usage map
|
|
338
|
+
* 2. Preload type references for files with exports (for performance)
|
|
339
|
+
* 3. Phase 1: Quick filter using import graph (O(1) lookup)
|
|
340
|
+
* 4. Phase 2: Deep AST analysis for potentially unused symbols (limited scope)
|
|
341
|
+
* 5. Find orphaned files (files with exports but no importers)
|
|
342
|
+
* 6. Check for unused functions in new code (if provided)
|
|
343
|
+
*
|
|
344
|
+
* Performance optimizations:
|
|
345
|
+
* - Limits number of exports to check (MAX_EXPORTS_TO_CHECK = 300)
|
|
346
|
+
* - Limits deep analysis scope (MAX_DEEP_ANALYSIS = 150)
|
|
347
|
+
* - Uses batch processing for file preloading and analysis
|
|
348
|
+
* - Maintains caches for file contents, type references, and symbol usage
|
|
349
|
+
* - Returns at most 30 issues to avoid overwhelming output
|
|
350
|
+
*
|
|
351
|
+
* @param context - Project context with file and dependency information
|
|
352
|
+
* @param newCode - Optional new code to check for unused functions
|
|
353
|
+
* @returns Array of dead code issues found
|
|
354
|
+
*/
|
|
355
|
+
export async function detectDeadCode(context, newCode, filePathFilter, perFileOnly) {
|
|
356
|
+
const issues = [];
|
|
357
|
+
// Per-file mode: only check for unused functions/constants in newCode.
|
|
358
|
+
// Skip ALL project-wide scans (unused exports, orphaned files) — these are slow,
|
|
359
|
+
// race-prone during rapid vibecoding, and belong in the initial health check.
|
|
360
|
+
if (perFileOnly && newCode) {
|
|
361
|
+
return detectUnusedLocals(newCode, "(new code)");
|
|
362
|
+
}
|
|
363
|
+
else if (perFileOnly) {
|
|
364
|
+
return issues; // No newCode to check
|
|
365
|
+
}
|
|
366
|
+
// Load ignore patterns
|
|
367
|
+
const ignorePatterns = await loadIgnorePatterns(context.projectPath);
|
|
368
|
+
// Clear only the working caches, preserve mtime cache for incremental analysis
|
|
369
|
+
fileContentCache.clear();
|
|
370
|
+
typeReferencesCache.clear();
|
|
371
|
+
symbolUsageCache.clear();
|
|
372
|
+
// fileMtimeCache is preserved to enable incremental analysis
|
|
373
|
+
// Build map of which symbols are imported FROM each file
|
|
374
|
+
const symbolsImportedFromFile = new Map();
|
|
375
|
+
const jsxUsedComponents = new Set();
|
|
376
|
+
// Track all imports across the project
|
|
377
|
+
for (const dep of context.dependencies) {
|
|
378
|
+
if (!symbolsImportedFromFile.has(dep.to)) {
|
|
379
|
+
symbolsImportedFromFile.set(dep.to, new Set());
|
|
380
|
+
}
|
|
381
|
+
for (const sym of dep.importedSymbols) {
|
|
382
|
+
symbolsImportedFromFile.get(dep.to).add(sym);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
// Track JSX and Wildcard usage + build raw import name set for safety checks
|
|
386
|
+
const wildcardImportedModules = new Map(); // Local Name -> Module Path
|
|
387
|
+
const allFileKeysEarly = Array.from(context.files.keys()); // Cache once for all resolveImport calls
|
|
388
|
+
// allRawImportedNames: ALL symbol names imported anywhere, from raw file analysis.
|
|
389
|
+
// Unlike symbolsImportedFromFile (which depends on dependency resolution), this set
|
|
390
|
+
// is always up-to-date because it comes from the parsed import statements directly.
|
|
391
|
+
// Used as a safety net in orphaned file detection to catch graph staleness.
|
|
392
|
+
const allRawImportedNames = new Set();
|
|
393
|
+
for (const [filePath, fileInfo] of context.files) {
|
|
394
|
+
for (const imp of fileInfo.imports) {
|
|
395
|
+
if (imp.defaultImport && /^[A-Z]/.test(imp.defaultImport)) {
|
|
396
|
+
jsxUsedComponents.add(imp.defaultImport);
|
|
397
|
+
}
|
|
398
|
+
for (const name of imp.namedImports) {
|
|
399
|
+
if (/^[A-Z]/.test(name)) {
|
|
400
|
+
jsxUsedComponents.add(name);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Build raw import name set (all named + default imports regardless of resolution)
|
|
404
|
+
if (imp.defaultImport) {
|
|
405
|
+
allRawImportedNames.add(imp.defaultImport);
|
|
406
|
+
}
|
|
407
|
+
for (const name of imp.namedImports) {
|
|
408
|
+
allRawImportedNames.add(name);
|
|
409
|
+
}
|
|
410
|
+
// Track Wildcard imports: import * as utils from './utils'
|
|
411
|
+
if (imp.namespaceImport) {
|
|
412
|
+
const resolved = resolveImport(imp.source, filePath, allFileKeysEarly);
|
|
413
|
+
if (resolved) {
|
|
414
|
+
wildcardImportedModules.set(imp.namespaceImport, resolved);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// OPTIMIZED: Use cached context data instead of re-parsing files
|
|
420
|
+
// The context already has imports, exports, and symbols from project analysis
|
|
421
|
+
logger.debug("Using cached context data for dead code detection...");
|
|
422
|
+
// Build string literal usage from cached keywords in context
|
|
423
|
+
// This is much faster than re-reading and parsing all files
|
|
424
|
+
const stringLiteralUsage = new Set();
|
|
425
|
+
for (const [filePath, fileInfo] of context.files) {
|
|
426
|
+
// Use keywords from context (already extracted during project build)
|
|
427
|
+
for (const keyword of fileInfo.keywords) {
|
|
428
|
+
if (keyword.length >= 3) {
|
|
429
|
+
stringLiteralUsage.add(keyword);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// OPTIMIZED: Use cached imports from context for wildcard usage tracking
|
|
434
|
+
// Pre-build a reverse index: import_source → Set<namedImport> (single O(n) pass)
|
|
435
|
+
// This replaces the previous O(n²) nested loop over all files
|
|
436
|
+
const sourceToNamedImports = new Map();
|
|
437
|
+
for (const [, fileInfo] of context.files) {
|
|
438
|
+
for (const imp of fileInfo.imports) {
|
|
439
|
+
if (imp.namedImports.length > 0) {
|
|
440
|
+
if (!sourceToNamedImports.has(imp.source)) {
|
|
441
|
+
sourceToNamedImports.set(imp.source, new Set());
|
|
442
|
+
}
|
|
443
|
+
const set = sourceToNamedImports.get(imp.source);
|
|
444
|
+
for (const sym of imp.namedImports) {
|
|
445
|
+
set.add(sym);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// Now resolve namespace imports using the pre-built index (O(n) pass)
|
|
451
|
+
const allFileKeys = Array.from(context.files.keys()); // Cache to avoid repeated Array.from
|
|
452
|
+
for (const [filePath, fileInfo] of context.files) {
|
|
453
|
+
for (const imp of fileInfo.imports) {
|
|
454
|
+
if (imp.namespaceImport) {
|
|
455
|
+
const resolved = resolveImport(imp.source, filePath, allFileKeys);
|
|
456
|
+
if (resolved) {
|
|
457
|
+
const namedImports = sourceToNamedImports.get(imp.source);
|
|
458
|
+
if (namedImports && namedImports.size > 0) {
|
|
459
|
+
if (!symbolsImportedFromFile.has(resolved)) {
|
|
460
|
+
symbolsImportedFromFile.set(resolved, new Set());
|
|
461
|
+
}
|
|
462
|
+
const importSet = symbolsImportedFromFile.get(resolved);
|
|
463
|
+
for (const sym of namedImports) {
|
|
464
|
+
importSet.add(sym);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Identify files that need deep analysis (have exports but not in import graph)
|
|
472
|
+
const filesToPreload = new Set();
|
|
473
|
+
for (const [filePath, fileInfo] of context.files) {
|
|
474
|
+
if (fileInfo.isTest || fileInfo.isConfig || fileInfo.isEntryPoint)
|
|
475
|
+
continue;
|
|
476
|
+
if (fileInfo.exports.length === 0 && !fileInfo.symbols.some((s) => s.exported))
|
|
477
|
+
continue;
|
|
478
|
+
// Only deep-analyze if exports aren't in import graph
|
|
479
|
+
const hasUnimportedExports = fileInfo.exports.some(exp => !isSymbolImported(exp.name, filePath, symbolsImportedFromFile));
|
|
480
|
+
if (hasUnimportedExports) {
|
|
481
|
+
filesToPreload.add(filePath);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
logger.debug(`Using cached context: ${stringLiteralUsage.size} keywords, ${filesToPreload.size} files need deep analysis`);
|
|
485
|
+
// Collect all exported symbols to check
|
|
486
|
+
const exportsToCheck = [];
|
|
487
|
+
for (const [filePath, fileInfo] of context.files) {
|
|
488
|
+
if (fileInfo.isTest || fileInfo.isConfig || fileInfo.isEntryPoint)
|
|
489
|
+
continue;
|
|
490
|
+
// Check against ignore patterns
|
|
491
|
+
if (isIgnored(fileInfo.relativePath, ignorePatterns))
|
|
492
|
+
continue;
|
|
493
|
+
// Apply scope filter (e.g., only backend or frontend files)
|
|
494
|
+
if (filePathFilter && !filePathFilter(filePath))
|
|
495
|
+
continue;
|
|
496
|
+
// Collect from exports list
|
|
497
|
+
for (const exp of fileInfo.exports) {
|
|
498
|
+
const symbol = fileInfo.symbols.find((s) => s.name === exp.name);
|
|
499
|
+
// SKIP type-only exports (interfaces, type aliases, enums)
|
|
500
|
+
// These are compile-time constructs and don't generate runtime code
|
|
501
|
+
// They should not be flagged as "dead code"
|
|
502
|
+
if (symbol?.kind === "interface" || symbol?.kind === "type") {
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
exportsToCheck.push({
|
|
506
|
+
name: exp.name,
|
|
507
|
+
file: filePath,
|
|
508
|
+
relativePath: fileInfo.relativePath,
|
|
509
|
+
kind: symbol?.kind,
|
|
510
|
+
scope: symbol?.scope,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
// Collect from symbols with export flag
|
|
514
|
+
for (const sym of fileInfo.symbols) {
|
|
515
|
+
if (!sym.exported)
|
|
516
|
+
continue;
|
|
517
|
+
if (exportsToCheck.some((e) => e.name === sym.name && e.file === filePath))
|
|
518
|
+
continue;
|
|
519
|
+
// SKIP type-only symbols (interfaces, type aliases, enums)
|
|
520
|
+
// These are compile-time constructs and don't generate runtime code
|
|
521
|
+
if (sym.kind === "interface" || sym.kind === "type") {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
exportsToCheck.push({
|
|
525
|
+
name: sym.name,
|
|
526
|
+
file: filePath,
|
|
527
|
+
relativePath: fileInfo.relativePath,
|
|
528
|
+
kind: sym.kind,
|
|
529
|
+
scope: sym.scope,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
// Limit the number of exports to check to prevent timeout
|
|
534
|
+
const MAX_EXPORTS_TO_CHECK = 300; // Increased for better coverage
|
|
535
|
+
let limitWarning = "";
|
|
536
|
+
if (exportsToCheck.length > MAX_EXPORTS_TO_CHECK) {
|
|
537
|
+
limitWarning = `Note: Limited analysis to ${MAX_EXPORTS_TO_CHECK} of ${exportsToCheck.length} exports for performance. Run on smaller scopes for complete coverage.`;
|
|
538
|
+
logger.warn(`Too many exports (${exportsToCheck.length}), limiting to ${MAX_EXPORTS_TO_CHECK} for performance`);
|
|
539
|
+
// Prioritize checking exports from non-test, non-config files
|
|
540
|
+
exportsToCheck.sort((a, b) => {
|
|
541
|
+
const aIsTest = a.file.includes("test") || a.file.includes("spec");
|
|
542
|
+
const bIsTest = b.file.includes("test") || b.file.includes("spec");
|
|
543
|
+
if (aIsTest && !bIsTest)
|
|
544
|
+
return 1;
|
|
545
|
+
if (!aIsTest && bIsTest)
|
|
546
|
+
return -1;
|
|
547
|
+
return 0;
|
|
548
|
+
});
|
|
549
|
+
exportsToCheck.splice(MAX_EXPORTS_TO_CHECK);
|
|
550
|
+
}
|
|
551
|
+
// Check exports with a hybrid approach: fast import check + selective deep analysis
|
|
552
|
+
logger.debug(`Checking ${exportsToCheck.length} exports using hybrid analysis...`);
|
|
553
|
+
// Phase 1: Quick filter - check import graph (fast, O(1) lookup)
|
|
554
|
+
const potentiallyUnused = [];
|
|
555
|
+
for (const exp of exportsToCheck) {
|
|
556
|
+
const isPascalCase = /^[A-Z][a-zA-Z0-9]*$/.test(exp.name);
|
|
557
|
+
const isUsedAsJSX = isPascalCase && jsxUsedComponents.has(exp.name);
|
|
558
|
+
// Skip JSX components that are used
|
|
559
|
+
if (isUsedAsJSX)
|
|
560
|
+
continue;
|
|
561
|
+
// Fast check: Is it directly imported or reflectively referenced?
|
|
562
|
+
const isImported = isSymbolImported(exp.name, exp.file, symbolsImportedFromFile);
|
|
563
|
+
// Reflective usage check: Is this symbol name used in any string literal?
|
|
564
|
+
// This perfectly handles dynamic registration (e.g. tools, handlers, routes)
|
|
565
|
+
const isReflective = stringLiteralUsage.has(exp.name);
|
|
566
|
+
if (!isImported && !isReflective) {
|
|
567
|
+
// Potentially unused - needs deeper analysis
|
|
568
|
+
potentiallyUnused.push(exp);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
logger.debug(`Phase 1: ${potentiallyUnused.length}/${exportsToCheck.length} exports not imported, running deep analysis...`);
|
|
572
|
+
// Log if we're checking a limited set
|
|
573
|
+
if (exportsToCheck.length < context.files.size * 5) {
|
|
574
|
+
// Rough heuristic: if we're checking fewer exports than 5x files, we likely hit a limit
|
|
575
|
+
logger.info(`Analyzing ${exportsToCheck.length} exports (may be limited for performance)`);
|
|
576
|
+
}
|
|
577
|
+
// Phase 2: Deep analysis for potentially unused exports (in smaller batches with limits)
|
|
578
|
+
const MAX_DEEP_ANALYSIS = 150; // Increased for better coverage
|
|
579
|
+
const toAnalyze = potentiallyUnused.slice(0, MAX_DEEP_ANALYSIS);
|
|
580
|
+
if (potentiallyUnused.length > MAX_DEEP_ANALYSIS) {
|
|
581
|
+
const deepLimitWarning = `Limited deep analysis to ${MAX_DEEP_ANALYSIS} of ${potentiallyUnused.length} potentially unused exports.`;
|
|
582
|
+
limitWarning =
|
|
583
|
+
limitWarning ? `${limitWarning} ${deepLimitWarning}` : deepLimitWarning;
|
|
584
|
+
logger.warn(deepLimitWarning);
|
|
585
|
+
}
|
|
586
|
+
// Phase 2: Deep analysis - process potentially unused exports in smaller batches
|
|
587
|
+
// This prevents overwhelming the event loop and allows for periodic yielding
|
|
588
|
+
const DEEP_BATCH_SIZE = 20;
|
|
589
|
+
for (let i = 0; i < toAnalyze.length; i += DEEP_BATCH_SIZE) {
|
|
590
|
+
const batch = toAnalyze.slice(i, i + DEEP_BATCH_SIZE);
|
|
591
|
+
const batchResults = await Promise.all(batch.map(async (exp) => {
|
|
592
|
+
// Check cache first to avoid redundant work
|
|
593
|
+
const cacheKey = `${exp.file}:${exp.name}`;
|
|
594
|
+
const cached = symbolUsageCache.get(cacheKey);
|
|
595
|
+
if (cached !== undefined) {
|
|
596
|
+
return cached ? null : ({
|
|
597
|
+
type: "unusedExport",
|
|
598
|
+
severity: "low", // Downgrade from medium to low
|
|
599
|
+
name: exp.name,
|
|
600
|
+
file: exp.relativePath,
|
|
601
|
+
message: `Export '${exp.name}' is never used anywhere in the codebase`,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
// Check same-file usage first (fastest - uses cached file content)
|
|
605
|
+
const isUsedInSameFile = await isSymbolUsedInSameFileExports(exp.name, exp.file, context, symbolsImportedFromFile);
|
|
606
|
+
if (isUsedInSameFile) {
|
|
607
|
+
symbolUsageCache.set(cacheKey, true);
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
// Then check type references (uses preloaded cache)
|
|
611
|
+
const isTypeReferenced = await isSymbolTypeReferenced(exp.name, exp.file, context);
|
|
612
|
+
if (isTypeReferenced) {
|
|
613
|
+
symbolUsageCache.set(cacheKey, true);
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
// Finally check function calls (most expensive)
|
|
617
|
+
const isCalled = await isSymbolCalledOrInstantiated(exp.name, context, exp.scope);
|
|
618
|
+
if (isCalled) {
|
|
619
|
+
symbolUsageCache.set(cacheKey, true);
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
// Truly unused
|
|
623
|
+
symbolUsageCache.set(cacheKey, false);
|
|
624
|
+
return {
|
|
625
|
+
type: "unusedExport",
|
|
626
|
+
severity: "low", // Downgrade from medium to low
|
|
627
|
+
name: exp.name,
|
|
628
|
+
file: exp.relativePath,
|
|
629
|
+
message: `Export '${exp.name}' is never used anywhere in the codebase`,
|
|
630
|
+
};
|
|
631
|
+
}));
|
|
632
|
+
// Collect non-null results (truly unused exports)
|
|
633
|
+
for (const result of batchResults) {
|
|
634
|
+
if (result &&
|
|
635
|
+
!issues.some((i) => i.name === result.name && i.file === result.file)) {
|
|
636
|
+
issues.push(result);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// Yield to event loop after each batch
|
|
640
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
641
|
+
}
|
|
642
|
+
// Find orphaned files (files with exports but never imported AND no symbols used)
|
|
643
|
+
for (const [filePath, fileInfo] of context.files) {
|
|
644
|
+
if (fileInfo.isTest || fileInfo.isConfig || fileInfo.isEntryPoint)
|
|
645
|
+
continue;
|
|
646
|
+
const importers = context.reverseImportGraph.get(filePath) || [];
|
|
647
|
+
const hasExports = fileInfo.exports.length > 0 || fileInfo.symbols.some((s) => s.exported);
|
|
648
|
+
if (importers.length === 0 && hasExports) {
|
|
649
|
+
// Python-specific: Check if this file is re-exported via __init__.py
|
|
650
|
+
// In Python, files exported through __init__.py are NOT orphaned
|
|
651
|
+
if (context.language === "python") {
|
|
652
|
+
const dir = path.dirname(filePath);
|
|
653
|
+
const initPy = path.join(dir, "__init__.py");
|
|
654
|
+
const initInfo = context.files.get(initPy);
|
|
655
|
+
if (initInfo) {
|
|
656
|
+
// Check if __init__.py imports from this module
|
|
657
|
+
const moduleName = path.basename(filePath, ".py");
|
|
658
|
+
const isReExported = initInfo.imports.some((imp) => imp.source === `.${moduleName}` ||
|
|
659
|
+
imp.source === `./${moduleName}` ||
|
|
660
|
+
imp.source === moduleName ||
|
|
661
|
+
imp.source.endsWith(`.${moduleName}`) ||
|
|
662
|
+
imp.namedImports.some((n) => fileInfo.symbols.some((s) => s.name === n && s.exported)));
|
|
663
|
+
if (isReExported)
|
|
664
|
+
continue; // Not orphaned — re-exported via __init__.py
|
|
665
|
+
// Also check if __init__.py content references this module via text scan
|
|
666
|
+
// Handles: from .module import *, import patterns in __init__.py
|
|
667
|
+
const initContent = fileContentCache.get(initPy);
|
|
668
|
+
if (initContent) {
|
|
669
|
+
const regex = new RegExp(`\\b${moduleName}\\b`);
|
|
670
|
+
if (regex.test(initContent))
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
// Try to read __init__.py to check
|
|
675
|
+
try {
|
|
676
|
+
const content = await fs.readFile(initPy, "utf-8");
|
|
677
|
+
fileContentCache.set(initPy, content);
|
|
678
|
+
const regex = new RegExp(`\\b${moduleName}\\b`);
|
|
679
|
+
if (regex.test(content))
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
catch {
|
|
683
|
+
// Can't read, proceed with orphan check
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
// Python-specific: Check if any file imports this module by name from the parent package.
|
|
689
|
+
// e.g., `from app.services import planning_service` resolves to __init__.py in the
|
|
690
|
+
// dependency graph, but actually means planning_service.py is being used.
|
|
691
|
+
if (context.language === "python") {
|
|
692
|
+
const moduleName = path.basename(filePath, ".py");
|
|
693
|
+
let isModuleImported = false;
|
|
694
|
+
for (const [, otherFileInfo] of context.files) {
|
|
695
|
+
for (const imp of otherFileInfo.imports) {
|
|
696
|
+
if (imp.namedImports.includes(moduleName)) {
|
|
697
|
+
isModuleImported = true;
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (isModuleImported)
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
if (isModuleImported)
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
// Double-check: are ANY of this file's exports used in OTHER files?
|
|
708
|
+
// Important: only check cross-file usage (imports, type refs, calls from other files).
|
|
709
|
+
// Do NOT use isSymbolUsedAnywhere here — its same-file scope check
|
|
710
|
+
// (isSymbolUsedInSameFileExports) falsely marks files as "used" when methods
|
|
711
|
+
// have an exported parent object, even though no other file imports from this file.
|
|
712
|
+
let anyExportUsed = false;
|
|
713
|
+
for (const exp of fileInfo.exports) {
|
|
714
|
+
// Check: Is it directly imported by another file?
|
|
715
|
+
// This is the ONLY reliable check for orphaned files because it scopes
|
|
716
|
+
// by defining file. isSymbolTypeReferenced and isSymbolCalledOrInstantiated
|
|
717
|
+
// match by NAME only, producing false positives from name collisions
|
|
718
|
+
// (e.g., "buildQueryString" used in another file from a different definition).
|
|
719
|
+
// Since importers.length === 0 (reverseImportGraph), we already know no file
|
|
720
|
+
// imports from this file, but symbolsImportedFromFile (from dependencies)
|
|
721
|
+
// provides a second opinion.
|
|
722
|
+
if (isSymbolImported(exp.name, filePath, symbolsImportedFromFile)) {
|
|
723
|
+
anyExportUsed = true;
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
// Safety net: check against RAW import data (immune to dependency graph staleness).
|
|
728
|
+
// During rapid vibecoding, the dependency graph can be temporarily stale
|
|
729
|
+
// (e.g., reverseImportGraph missing entries because refreshFileContext hasn't
|
|
730
|
+
// re-resolved all importers). Raw import names come directly from file analysis
|
|
731
|
+
// and are always up-to-date.
|
|
732
|
+
if (!anyExportUsed) {
|
|
733
|
+
for (const exp of fileInfo.exports) {
|
|
734
|
+
if (jsxUsedComponents.has(exp.name) || allRawImportedNames.has(exp.name)) {
|
|
735
|
+
anyExportUsed = true;
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
// Also check symbols (some exports are tracked as symbols, not in exports array)
|
|
740
|
+
if (!anyExportUsed) {
|
|
741
|
+
for (const sym of fileInfo.symbols) {
|
|
742
|
+
if (sym.exported && (jsxUsedComponents.has(sym.name) || allRawImportedNames.has(sym.name))) {
|
|
743
|
+
anyExportUsed = true;
|
|
744
|
+
break;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (!anyExportUsed) {
|
|
750
|
+
const alreadyFlagged = issues.some((i) => i.type === "orphanedFile" && i.file === fileInfo.relativePath);
|
|
751
|
+
if (!alreadyFlagged) {
|
|
752
|
+
issues.push({
|
|
753
|
+
type: "orphanedFile",
|
|
754
|
+
severity: "low",
|
|
755
|
+
name: fileInfo.relativePath,
|
|
756
|
+
file: fileInfo.relativePath,
|
|
757
|
+
message: `File has exports but nothing is used anywhere`,
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
// Check for unused functions in new code
|
|
764
|
+
if (newCode) {
|
|
765
|
+
const definedSymbols = new Set();
|
|
766
|
+
const usedSymbols = new Set();
|
|
767
|
+
const defPatterns = [
|
|
768
|
+
{ pattern: /function\s+([a-zA-Z0-9_$]+)\s*\(/g, type: "function" },
|
|
769
|
+
{ pattern: /(?:const|let|var)\s+([a-zA-Z0-9_$]+)\s*=\s*(?:async\s*)?\(/g, type: "function" },
|
|
770
|
+
{ pattern: /(?:const|let|var)\s+([a-zA-Z0-9_$]+)\s*=\s*.*=>/g, type: "function" },
|
|
771
|
+
{ pattern: /(?:const|let|var)\s+([A-Z0-9_$]{3,})\s*=/g, type: "constant" },
|
|
772
|
+
{ pattern: /def\s+([a-zA-Z0-9_$]+)\s*\(/g, type: "function" },
|
|
773
|
+
{ pattern: /([A-Z0-9_$]{3,})\s*=\s*/g, type: "constant" }, // Python constants
|
|
774
|
+
];
|
|
775
|
+
for (const { pattern, type } of defPatterns) {
|
|
776
|
+
let match;
|
|
777
|
+
while ((match = pattern.exec(newCode)) !== null) {
|
|
778
|
+
definedSymbols.add({ name: match[1], type });
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const usagePattern = /\b([a-zA-Z0-9_$]+)\b/g;
|
|
782
|
+
let match;
|
|
783
|
+
while ((match = usagePattern.exec(newCode)) !== null) {
|
|
784
|
+
usedSymbols.add(match[1]);
|
|
785
|
+
}
|
|
786
|
+
for (const sym of definedSymbols) {
|
|
787
|
+
// For each defined symbol, we expect at least 2 occurrences of the name
|
|
788
|
+
// (one for definition, one for usage).
|
|
789
|
+
const count = (newCode.match(new RegExp(`\\b${sym.name}\\b`, "g")) || []).length;
|
|
790
|
+
if (count <= 1) {
|
|
791
|
+
issues.push({
|
|
792
|
+
type: sym.type === "function" ? "unusedFunction" : "unusedExport",
|
|
793
|
+
severity: "medium",
|
|
794
|
+
name: sym.name,
|
|
795
|
+
file: "(new code)",
|
|
796
|
+
message: `${sym.type === "function" ? "Function" : "Constant"} '${sym.name}' is defined but never used in this code`,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
// Clear cache after scan
|
|
802
|
+
fileContentCache.clear();
|
|
803
|
+
// Add informational message if we hit limits
|
|
804
|
+
if (limitWarning) {
|
|
805
|
+
logger.info(`Dead code analysis: ${limitWarning}`);
|
|
806
|
+
}
|
|
807
|
+
// Separate orphanedFile issues from other dead code issues.
|
|
808
|
+
// Limit non-orphan issues (unusedExport, unusedFunction) to 30 for performance,
|
|
809
|
+
// but always include ALL orphanedFile entries — they are high-signal and cheap.
|
|
810
|
+
const orphanIssues = issues.filter(i => i.type === "orphanedFile");
|
|
811
|
+
const otherIssues = issues.filter(i => i.type !== "orphanedFile");
|
|
812
|
+
return [...otherIssues.slice(0, 30), ...orphanIssues];
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Clear all dead code detection caches
|
|
816
|
+
* Should be called between test runs to prevent interference
|
|
817
|
+
* Note: fileMtimeCache is preserved to enable incremental analysis
|
|
818
|
+
*/
|
|
819
|
+
export function clearDeadCodeCaches() {
|
|
820
|
+
fileContentCache.clear();
|
|
821
|
+
typeReferencesCache.clear();
|
|
822
|
+
symbolUsageCache.clear();
|
|
823
|
+
// fileMtimeCache is intentionally NOT cleared to enable incremental analysis
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Force clear ALL caches including mtime cache
|
|
827
|
+
* Use this when you want to force a full re-scan
|
|
828
|
+
*/
|
|
829
|
+
export function forceFullScan() {
|
|
830
|
+
fileContentCache.clear();
|
|
831
|
+
typeReferencesCache.clear();
|
|
832
|
+
symbolUsageCache.clear();
|
|
833
|
+
fileMtimeCache.clear();
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Load ignore patterns from .vibeguardignore or .gitignore
|
|
837
|
+
*/
|
|
838
|
+
async function loadIgnorePatterns(projectPath) {
|
|
839
|
+
const patterns = [
|
|
840
|
+
"**/node_modules/**",
|
|
841
|
+
"**/dist/**",
|
|
842
|
+
"**/build/**",
|
|
843
|
+
"**/.git/**",
|
|
844
|
+
];
|
|
845
|
+
try {
|
|
846
|
+
const ignoreFile = path.join(projectPath, ".vibeguardignore");
|
|
847
|
+
const content = await fs.readFile(ignoreFile, "utf-8");
|
|
848
|
+
patterns.push(...content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")));
|
|
849
|
+
}
|
|
850
|
+
catch {
|
|
851
|
+
// Fallback to basic patterns if file doesn't exist
|
|
852
|
+
}
|
|
853
|
+
return patterns;
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Check if a file is ignored
|
|
857
|
+
*/
|
|
858
|
+
function isIgnored(relativePath, patterns) {
|
|
859
|
+
return patterns.some((pattern) => minimatch(relativePath, pattern, { dot: true }));
|
|
860
|
+
}
|
|
861
|
+
//# sourceMappingURL=deadCode.js.map
|