circle-ir-ai 1.1.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/CHANGELOG.md +105 -0
- package/LICENSE +15 -0
- package/README.md +336 -0
- package/dist/action-queue/aggregator.d.ts +40 -0
- package/dist/action-queue/aggregator.d.ts.map +1 -0
- package/dist/action-queue/aggregator.js +375 -0
- package/dist/action-queue/aggregator.js.map +1 -0
- package/dist/action-queue/index.d.ts +14 -0
- package/dist/action-queue/index.d.ts.map +1 -0
- package/dist/action-queue/index.js +17 -0
- package/dist/action-queue/index.js.map +1 -0
- package/dist/action-queue/queue.d.ts +74 -0
- package/dist/action-queue/queue.d.ts.map +1 -0
- package/dist/action-queue/queue.js +433 -0
- package/dist/action-queue/queue.js.map +1 -0
- package/dist/action-queue/types.d.ts +162 -0
- package/dist/action-queue/types.d.ts.map +1 -0
- package/dist/action-queue/types.js +44 -0
- package/dist/action-queue/types.js.map +1 -0
- package/dist/agents/enrichment-agent.d.ts +16 -0
- package/dist/agents/enrichment-agent.d.ts.map +1 -0
- package/dist/agents/enrichment-agent.js +102 -0
- package/dist/agents/enrichment-agent.js.map +1 -0
- package/dist/agents/index.d.ts +12 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +15 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/mastra/agents.d.ts +373 -0
- package/dist/agents/mastra/agents.d.ts.map +1 -0
- package/dist/agents/mastra/agents.js +347 -0
- package/dist/agents/mastra/agents.js.map +1 -0
- package/dist/agents/mastra/index.d.ts +12 -0
- package/dist/agents/mastra/index.d.ts.map +1 -0
- package/dist/agents/mastra/index.js +17 -0
- package/dist/agents/mastra/index.js.map +1 -0
- package/dist/agents/mastra/instance.d.ts +383 -0
- package/dist/agents/mastra/instance.d.ts.map +1 -0
- package/dist/agents/mastra/instance.js +37 -0
- package/dist/agents/mastra/instance.js.map +1 -0
- package/dist/agents/mastra/steps.d.ts +300 -0
- package/dist/agents/mastra/steps.d.ts.map +1 -0
- package/dist/agents/mastra/steps.js +468 -0
- package/dist/agents/mastra/steps.js.map +1 -0
- package/dist/agents/mastra/swarm.d.ts +106 -0
- package/dist/agents/mastra/swarm.d.ts.map +1 -0
- package/dist/agents/mastra/swarm.js +501 -0
- package/dist/agents/mastra/swarm.js.map +1 -0
- package/dist/agents/mastra/workflow.d.ts +81 -0
- package/dist/agents/mastra/workflow.d.ts.map +1 -0
- package/dist/agents/mastra/workflow.js +460 -0
- package/dist/agents/mastra/workflow.js.map +1 -0
- package/dist/agents/multi/agents/security.d.ts +29 -0
- package/dist/agents/multi/agents/security.d.ts.map +1 -0
- package/dist/agents/multi/agents/security.js +830 -0
- package/dist/agents/multi/agents/security.js.map +1 -0
- package/dist/agents/multi/extractor.d.ts +21 -0
- package/dist/agents/multi/extractor.d.ts.map +1 -0
- package/dist/agents/multi/extractor.js +483 -0
- package/dist/agents/multi/extractor.js.map +1 -0
- package/dist/agents/multi/index.d.ts +32 -0
- package/dist/agents/multi/index.d.ts.map +1 -0
- package/dist/agents/multi/index.js +34 -0
- package/dist/agents/multi/index.js.map +1 -0
- package/dist/agents/multi/runner.d.ts +79 -0
- package/dist/agents/multi/runner.d.ts.map +1 -0
- package/dist/agents/multi/runner.js +323 -0
- package/dist/agents/multi/runner.js.map +1 -0
- package/dist/agents/security-agent.d.ts +16 -0
- package/dist/agents/security-agent.d.ts.map +1 -0
- package/dist/agents/security-agent.js +299 -0
- package/dist/agents/security-agent.js.map +1 -0
- package/dist/agents/types.d.ts +373 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +14 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/agents/verification-agent.d.ts +23 -0
- package/dist/agents/verification-agent.d.ts.map +1 -0
- package/dist/agents/verification-agent.js +217 -0
- package/dist/agents/verification-agent.js.map +1 -0
- package/dist/agents/workflow.d.ts +30 -0
- package/dist/agents/workflow.d.ts.map +1 -0
- package/dist/agents/workflow.js +79 -0
- package/dist/agents/workflow.js.map +1 -0
- package/dist/analysis/enriched.d.ts +16 -0
- package/dist/analysis/enriched.d.ts.map +1 -0
- package/dist/analysis/enriched.js +297 -0
- package/dist/analysis/enriched.js.map +1 -0
- package/dist/analysis/llm-correlated-predicates.d.ts +80 -0
- package/dist/analysis/llm-correlated-predicates.d.ts.map +1 -0
- package/dist/analysis/llm-correlated-predicates.js +255 -0
- package/dist/analysis/llm-correlated-predicates.js.map +1 -0
- package/dist/analysis/llm-cross-file-taint.d.ts +86 -0
- package/dist/analysis/llm-cross-file-taint.d.ts.map +1 -0
- package/dist/analysis/llm-cross-file-taint.js +264 -0
- package/dist/analysis/llm-cross-file-taint.js.map +1 -0
- package/dist/analysis/pattern-discovery.d.ts +79 -0
- package/dist/analysis/pattern-discovery.d.ts.map +1 -0
- package/dist/analysis/pattern-discovery.js +447 -0
- package/dist/analysis/pattern-discovery.js.map +1 -0
- package/dist/cache/file-cache.d.ts +89 -0
- package/dist/cache/file-cache.d.ts.map +1 -0
- package/dist/cache/file-cache.js +208 -0
- package/dist/cache/file-cache.js.map +1 -0
- package/dist/cache/index.d.ts +6 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +5 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cli/args.d.ts +52 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +422 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/colors.d.ts +31 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +80 -0
- package/dist/cli/colors.js.map +1 -0
- package/dist/cli/commands/analyze-skill.d.ts +33 -0
- package/dist/cli/commands/analyze-skill.d.ts.map +1 -0
- package/dist/cli/commands/analyze-skill.js +217 -0
- package/dist/cli/commands/analyze-skill.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +18 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +30 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/benchmark-runner.d.ts +42 -0
- package/dist/cli/commands/benchmark-runner.d.ts.map +1 -0
- package/dist/cli/commands/benchmark-runner.js +18 -0
- package/dist/cli/commands/benchmark-runner.js.map +1 -0
- package/dist/cli/commands/benchmark.d.ts +11 -0
- package/dist/cli/commands/benchmark.d.ts.map +1 -0
- package/dist/cli/commands/benchmark.js +90 -0
- package/dist/cli/commands/benchmark.js.map +1 -0
- package/dist/cli/commands/dead-code.d.ts +11 -0
- package/dist/cli/commands/dead-code.d.ts.map +1 -0
- package/dist/cli/commands/dead-code.js +65 -0
- package/dist/cli/commands/dead-code.js.map +1 -0
- package/dist/cli/commands/generate-spec.d.ts +11 -0
- package/dist/cli/commands/generate-spec.d.ts.map +1 -0
- package/dist/cli/commands/generate-spec.js +67 -0
- package/dist/cli/commands/generate-spec.js.map +1 -0
- package/dist/cli/commands/health.d.ts +11 -0
- package/dist/cli/commands/health.d.ts.map +1 -0
- package/dist/cli/commands/health.js +67 -0
- package/dist/cli/commands/health.js.map +1 -0
- package/dist/cli/commands/project.d.ts +21 -0
- package/dist/cli/commands/project.d.ts.map +1 -0
- package/dist/cli/commands/project.js +92 -0
- package/dist/cli/commands/project.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +11 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +68 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/commands/secrets.d.ts +11 -0
- package/dist/cli/commands/secrets.d.ts.map +1 -0
- package/dist/cli/commands/secrets.js +71 -0
- package/dist/cli/commands/secrets.js.map +1 -0
- package/dist/cli/commands/swarm.d.ts +20 -0
- package/dist/cli/commands/swarm.d.ts.map +1 -0
- package/dist/cli/commands/swarm.js +174 -0
- package/dist/cli/commands/swarm.js.map +1 -0
- package/dist/cli/config.d.ts +103 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +307 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/discovery.d.ts +31 -0
- package/dist/cli/discovery.d.ts.map +1 -0
- package/dist/cli/discovery.js +212 -0
- package/dist/cli/discovery.js.map +1 -0
- package/dist/cli/formatters/index.d.ts +15 -0
- package/dist/cli/formatters/index.d.ts.map +1 -0
- package/dist/cli/formatters/index.js +51 -0
- package/dist/cli/formatters/index.js.map +1 -0
- package/dist/cli/formatters/json.d.ts +11 -0
- package/dist/cli/formatters/json.d.ts.map +1 -0
- package/dist/cli/formatters/json.js +12 -0
- package/dist/cli/formatters/json.js.map +1 -0
- package/dist/cli/formatters/project-json.d.ts +11 -0
- package/dist/cli/formatters/project-json.d.ts.map +1 -0
- package/dist/cli/formatters/project-json.js +12 -0
- package/dist/cli/formatters/project-json.js.map +1 -0
- package/dist/cli/formatters/project-sarif.d.ts +11 -0
- package/dist/cli/formatters/project-sarif.d.ts.map +1 -0
- package/dist/cli/formatters/project-sarif.js +127 -0
- package/dist/cli/formatters/project-sarif.js.map +1 -0
- package/dist/cli/formatters/project-summary.d.ts +11 -0
- package/dist/cli/formatters/project-summary.d.ts.map +1 -0
- package/dist/cli/formatters/project-summary.js +202 -0
- package/dist/cli/formatters/project-summary.js.map +1 -0
- package/dist/cli/formatters/sarif-shared.d.ts +101 -0
- package/dist/cli/formatters/sarif-shared.d.ts.map +1 -0
- package/dist/cli/formatters/sarif-shared.js +57 -0
- package/dist/cli/formatters/sarif-shared.js.map +1 -0
- package/dist/cli/formatters/sarif.d.ts +12 -0
- package/dist/cli/formatters/sarif.d.ts.map +1 -0
- package/dist/cli/formatters/sarif.js +92 -0
- package/dist/cli/formatters/sarif.js.map +1 -0
- package/dist/cli/formatters/summary.d.ts +11 -0
- package/dist/cli/formatters/summary.d.ts.map +1 -0
- package/dist/cli/formatters/summary.js +240 -0
- package/dist/cli/formatters/summary.js.map +1 -0
- package/dist/cli/formatters/two-phase-summary.d.ts +11 -0
- package/dist/cli/formatters/two-phase-summary.d.ts.map +1 -0
- package/dist/cli/formatters/two-phase-summary.js +188 -0
- package/dist/cli/formatters/two-phase-summary.js.map +1 -0
- package/dist/cli/index.d.ts +15 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +555 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/components/clustering.d.ts +60 -0
- package/dist/components/clustering.d.ts.map +1 -0
- package/dist/components/clustering.js +129 -0
- package/dist/components/clustering.js.map +1 -0
- package/dist/components/enrichment.d.ts +45 -0
- package/dist/components/enrichment.d.ts.map +1 -0
- package/dist/components/enrichment.js +193 -0
- package/dist/components/enrichment.js.map +1 -0
- package/dist/components/index.d.ts +29 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +56 -0
- package/dist/components/index.js.map +1 -0
- package/dist/dead-code/detector.d.ts +200 -0
- package/dist/dead-code/detector.d.ts.map +1 -0
- package/dist/dead-code/detector.js +1003 -0
- package/dist/dead-code/detector.js.map +1 -0
- package/dist/dead-code/index.d.ts +7 -0
- package/dist/dead-code/index.d.ts.map +1 -0
- package/dist/dead-code/index.js +7 -0
- package/dist/dead-code/index.js.map +1 -0
- package/dist/extractors/index.d.ts +15 -0
- package/dist/extractors/index.d.ts.map +1 -0
- package/dist/extractors/index.js +14 -0
- package/dist/extractors/index.js.map +1 -0
- package/dist/extractors/natural-language.d.ts +46 -0
- package/dist/extractors/natural-language.d.ts.map +1 -0
- package/dist/extractors/natural-language.js +228 -0
- package/dist/extractors/natural-language.js.map +1 -0
- package/dist/extractors/tree-sitter.d.ts +33 -0
- package/dist/extractors/tree-sitter.d.ts.map +1 -0
- package/dist/extractors/tree-sitter.js +69 -0
- package/dist/extractors/tree-sitter.js.map +1 -0
- package/dist/extractors/types.d.ts +62 -0
- package/dist/extractors/types.d.ts.map +1 -0
- package/dist/extractors/types.js +54 -0
- package/dist/extractors/types.js.map +1 -0
- package/dist/health-score/calculator.d.ts +123 -0
- package/dist/health-score/calculator.d.ts.map +1 -0
- package/dist/health-score/calculator.js +444 -0
- package/dist/health-score/calculator.js.map +1 -0
- package/dist/health-score/index.d.ts +12 -0
- package/dist/health-score/index.d.ts.map +1 -0
- package/dist/health-score/index.js +14 -0
- package/dist/health-score/index.js.map +1 -0
- package/dist/health-score/metrics.d.ts +142 -0
- package/dist/health-score/metrics.d.ts.map +1 -0
- package/dist/health-score/metrics.js +332 -0
- package/dist/health-score/metrics.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/ax-client.d.ts +477 -0
- package/dist/llm/ax-client.d.ts.map +1 -0
- package/dist/llm/ax-client.js +1641 -0
- package/dist/llm/ax-client.js.map +1 -0
- package/dist/llm/config.d.ts +58 -0
- package/dist/llm/config.d.ts.map +1 -0
- package/dist/llm/config.js +97 -0
- package/dist/llm/config.js.map +1 -0
- package/dist/llm/discovery.d.ts +123 -0
- package/dist/llm/discovery.d.ts.map +1 -0
- package/dist/llm/discovery.js +505 -0
- package/dist/llm/discovery.js.map +1 -0
- package/dist/llm/enrichment.d.ts +108 -0
- package/dist/llm/enrichment.d.ts.map +1 -0
- package/dist/llm/enrichment.js +312 -0
- package/dist/llm/enrichment.js.map +1 -0
- package/dist/llm/index.d.ts +13 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +22 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/language-context.d.ts +64 -0
- package/dist/llm/language-context.d.ts.map +1 -0
- package/dist/llm/language-context.js +492 -0
- package/dist/llm/language-context.js.map +1 -0
- package/dist/llm/pattern-verification.d.ts +39 -0
- package/dist/llm/pattern-verification.d.ts.map +1 -0
- package/dist/llm/pattern-verification.js +127 -0
- package/dist/llm/pattern-verification.js.map +1 -0
- package/dist/llm/prompt-security.d.ts +120 -0
- package/dist/llm/prompt-security.d.ts.map +1 -0
- package/dist/llm/prompt-security.js +301 -0
- package/dist/llm/prompt-security.js.map +1 -0
- package/dist/llm/prompts/index.d.ts +31 -0
- package/dist/llm/prompts/index.d.ts.map +1 -0
- package/dist/llm/prompts/index.js +92 -0
- package/dist/llm/prompts/index.js.map +1 -0
- package/dist/llm/prompts/rust.d.ts +30 -0
- package/dist/llm/prompts/rust.d.ts.map +1 -0
- package/dist/llm/prompts/rust.js +121 -0
- package/dist/llm/prompts/rust.js.map +1 -0
- package/dist/llm/schemas.d.ts +892 -0
- package/dist/llm/schemas.d.ts.map +1 -0
- package/dist/llm/schemas.js +258 -0
- package/dist/llm/schemas.js.map +1 -0
- package/dist/llm/verification.d.ts +127 -0
- package/dist/llm/verification.d.ts.map +1 -0
- package/dist/llm/verification.js +394 -0
- package/dist/llm/verification.js.map +1 -0
- package/dist/project/analyzer.d.ts +30 -0
- package/dist/project/analyzer.d.ts.map +1 -0
- package/dist/project/analyzer.js +358 -0
- package/dist/project/analyzer.js.map +1 -0
- package/dist/project/call-graph.d.ts +22 -0
- package/dist/project/call-graph.d.ts.map +1 -0
- package/dist/project/call-graph.js +246 -0
- package/dist/project/call-graph.js.map +1 -0
- package/dist/project/index.d.ts +18 -0
- package/dist/project/index.d.ts.map +1 -0
- package/dist/project/index.js +20 -0
- package/dist/project/index.js.map +1 -0
- package/dist/project/taint-paths.d.ts +22 -0
- package/dist/project/taint-paths.d.ts.map +1 -0
- package/dist/project/taint-paths.js +265 -0
- package/dist/project/taint-paths.js.map +1 -0
- package/dist/project/two-phase-analyzer.d.ts +143 -0
- package/dist/project/two-phase-analyzer.d.ts.map +1 -0
- package/dist/project/two-phase-analyzer.js +646 -0
- package/dist/project/two-phase-analyzer.js.map +1 -0
- package/dist/project/type-hierarchy.d.ts +28 -0
- package/dist/project/type-hierarchy.d.ts.map +1 -0
- package/dist/project/type-hierarchy.js +218 -0
- package/dist/project/type-hierarchy.js.map +1 -0
- package/dist/secret-scan/index.d.ts +12 -0
- package/dist/secret-scan/index.d.ts.map +1 -0
- package/dist/secret-scan/index.js +14 -0
- package/dist/secret-scan/index.js.map +1 -0
- package/dist/secret-scan/patterns.d.ts +38 -0
- package/dist/secret-scan/patterns.d.ts.map +1 -0
- package/dist/secret-scan/patterns.js +473 -0
- package/dist/secret-scan/patterns.js.map +1 -0
- package/dist/secret-scan/scanner.d.ts +162 -0
- package/dist/secret-scan/scanner.d.ts.map +1 -0
- package/dist/secret-scan/scanner.js +511 -0
- package/dist/secret-scan/scanner.js.map +1 -0
- package/dist/security-scan/index.d.ts +12 -0
- package/dist/security-scan/index.d.ts.map +1 -0
- package/dist/security-scan/index.js +15 -0
- package/dist/security-scan/index.js.map +1 -0
- package/dist/security-scan/owasp-mapping.d.ts +29 -0
- package/dist/security-scan/owasp-mapping.d.ts.map +1 -0
- package/dist/security-scan/owasp-mapping.js +246 -0
- package/dist/security-scan/owasp-mapping.js.map +1 -0
- package/dist/security-scan/scanner.d.ts +204 -0
- package/dist/security-scan/scanner.d.ts.map +1 -0
- package/dist/security-scan/scanner.js +693 -0
- package/dist/security-scan/scanner.js.map +1 -0
- package/dist/security-scan/trend-tracker.d.ts +150 -0
- package/dist/security-scan/trend-tracker.d.ts.map +1 -0
- package/dist/security-scan/trend-tracker.js +299 -0
- package/dist/security-scan/trend-tracker.js.map +1 -0
- package/dist/skills/bundle-loader.d.ts +26 -0
- package/dist/skills/bundle-loader.d.ts.map +1 -0
- package/dist/skills/bundle-loader.js +284 -0
- package/dist/skills/bundle-loader.js.map +1 -0
- package/dist/skills/capability-mismatch.d.ts +21 -0
- package/dist/skills/capability-mismatch.d.ts.map +1 -0
- package/dist/skills/capability-mismatch.js +188 -0
- package/dist/skills/capability-mismatch.js.map +1 -0
- package/dist/skills/index.d.ts +10 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +9 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/skill-analyzer.d.ts +16 -0
- package/dist/skills/skill-analyzer.d.ts.map +1 -0
- package/dist/skills/skill-analyzer.js +361 -0
- package/dist/skills/skill-analyzer.js.map +1 -0
- package/dist/skills/types.d.ts +195 -0
- package/dist/skills/types.d.ts.map +1 -0
- package/dist/skills/types.js +7 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/specifica/conflict-resolver.d.ts +23 -0
- package/dist/specifica/conflict-resolver.d.ts.map +1 -0
- package/dist/specifica/conflict-resolver.js +129 -0
- package/dist/specifica/conflict-resolver.js.map +1 -0
- package/dist/specifica/evidence-aggregator.d.ts +33 -0
- package/dist/specifica/evidence-aggregator.d.ts.map +1 -0
- package/dist/specifica/evidence-aggregator.js +236 -0
- package/dist/specifica/evidence-aggregator.js.map +1 -0
- package/dist/specifica/evidence-extractor.d.ts +13 -0
- package/dist/specifica/evidence-extractor.d.ts.map +1 -0
- package/dist/specifica/evidence-extractor.js +431 -0
- package/dist/specifica/evidence-extractor.js.map +1 -0
- package/dist/specifica/feature-clustering.d.ts +19 -0
- package/dist/specifica/feature-clustering.d.ts.map +1 -0
- package/dist/specifica/feature-clustering.js +231 -0
- package/dist/specifica/feature-clustering.js.map +1 -0
- package/dist/specifica/generator.d.ts +16 -0
- package/dist/specifica/generator.d.ts.map +1 -0
- package/dist/specifica/generator.js +277 -0
- package/dist/specifica/generator.js.map +1 -0
- package/dist/specifica/index.d.ts +15 -0
- package/dist/specifica/index.d.ts.map +1 -0
- package/dist/specifica/index.js +18 -0
- package/dist/specifica/index.js.map +1 -0
- package/dist/specifica/prompts.d.ts +21 -0
- package/dist/specifica/prompts.d.ts.map +1 -0
- package/dist/specifica/prompts.js +196 -0
- package/dist/specifica/prompts.js.map +1 -0
- package/dist/specifica/spec-generator.d.ts +22 -0
- package/dist/specifica/spec-generator.d.ts.map +1 -0
- package/dist/specifica/spec-generator.js +229 -0
- package/dist/specifica/spec-generator.js.map +1 -0
- package/dist/specifica/types.d.ts +213 -0
- package/dist/specifica/types.d.ts.map +1 -0
- package/dist/specifica/types.js +7 -0
- package/dist/specifica/types.js.map +1 -0
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +51 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1,1003 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dead Code Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects unreachable code in a codebase by:
|
|
5
|
+
* 1. Identifying entry points (main methods, exports, HTTP handlers, etc.)
|
|
6
|
+
* 2. Building a call graph from those entry points
|
|
7
|
+
* 3. Finding functions/classes that are never reached
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { initAnalyzer, isAnalyzerInitialized, analyze, } from 'circle-ir';
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Entry Point Detection Patterns
|
|
14
|
+
// ============================================================================
|
|
15
|
+
/** Method names that indicate entry points */
|
|
16
|
+
const ENTRY_POINT_METHODS = new Set([
|
|
17
|
+
// Java main
|
|
18
|
+
'main',
|
|
19
|
+
// Servlet lifecycle
|
|
20
|
+
'init', 'destroy', 'service',
|
|
21
|
+
'doGet', 'doPost', 'doPut', 'doDelete', 'doHead', 'doOptions', 'doTrace',
|
|
22
|
+
// Spring lifecycle
|
|
23
|
+
'afterPropertiesSet', 'postConstruct', 'preDestroy',
|
|
24
|
+
// JUnit test methods (if includeTests is true)
|
|
25
|
+
'setUp', 'tearDown', 'beforeEach', 'afterEach', 'beforeAll', 'afterAll',
|
|
26
|
+
]);
|
|
27
|
+
/** Annotations that indicate entry points */
|
|
28
|
+
const ENTRY_POINT_ANNOTATIONS = new Set([
|
|
29
|
+
// Spring Web
|
|
30
|
+
'GetMapping', 'PostMapping', 'PutMapping', 'DeleteMapping', 'PatchMapping',
|
|
31
|
+
'RequestMapping', 'RestController', 'Controller',
|
|
32
|
+
// Spring lifecycle
|
|
33
|
+
'PostConstruct', 'PreDestroy', 'Bean', 'EventListener', 'Scheduled',
|
|
34
|
+
// JAX-RS
|
|
35
|
+
'GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH', 'Path',
|
|
36
|
+
// JUnit (if includeTests is true)
|
|
37
|
+
'Test', 'BeforeEach', 'AfterEach', 'BeforeAll', 'AfterAll', 'Before', 'After',
|
|
38
|
+
// Servlet
|
|
39
|
+
'WebServlet', 'WebFilter', 'WebListener',
|
|
40
|
+
// EJB
|
|
41
|
+
'Stateless', 'Stateful', 'Singleton', 'MessageDriven',
|
|
42
|
+
]);
|
|
43
|
+
/** Class-level annotations that make all public methods entry points */
|
|
44
|
+
const CLASS_ENTRY_POINT_ANNOTATIONS = new Set([
|
|
45
|
+
'RestController', 'Controller', 'Service', 'Component', 'Repository',
|
|
46
|
+
'Stateless', 'Stateful', 'Singleton',
|
|
47
|
+
'WebServlet', 'WebFilter',
|
|
48
|
+
]);
|
|
49
|
+
/** HTTP verbs used with framework receivers (JS/TS) */
|
|
50
|
+
const HTTP_VERB_METHODS = new Set([
|
|
51
|
+
'get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'use', 'all', 'route',
|
|
52
|
+
]);
|
|
53
|
+
/** Event emitter methods that take callback entry points (JS/TS) */
|
|
54
|
+
const EVENT_LISTENER_METHODS = new Set([
|
|
55
|
+
'on', 'once', 'addEventListener', 'addListener',
|
|
56
|
+
]);
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// Dead Code Detector Implementation
|
|
59
|
+
// ============================================================================
|
|
60
|
+
export class DeadCodeDetector {
|
|
61
|
+
verbose = false;
|
|
62
|
+
onProgress;
|
|
63
|
+
methodMap = new Map();
|
|
64
|
+
callGraph = new Map();
|
|
65
|
+
reverseCallGraph = new Map();
|
|
66
|
+
entryPoints = new Set();
|
|
67
|
+
reachable = new Set();
|
|
68
|
+
/**
|
|
69
|
+
* Detect dead code in a codebase.
|
|
70
|
+
*/
|
|
71
|
+
async detect(options) {
|
|
72
|
+
const startTime = Date.now();
|
|
73
|
+
this.verbose = options.verbose ?? false;
|
|
74
|
+
this.onProgress = options.onProgress;
|
|
75
|
+
// Reset state
|
|
76
|
+
this.methodMap.clear();
|
|
77
|
+
this.callGraph.clear();
|
|
78
|
+
this.reverseCallGraph.clear();
|
|
79
|
+
this.entryPoints.clear();
|
|
80
|
+
this.reachable.clear();
|
|
81
|
+
// Ensure analyzer is initialized
|
|
82
|
+
if (!isAnalyzerInitialized()) {
|
|
83
|
+
await initAnalyzer();
|
|
84
|
+
}
|
|
85
|
+
// Phase 1: Discover files
|
|
86
|
+
this.reportProgress({ phase: 'discover', filesProcessed: 0, totalFiles: 0 });
|
|
87
|
+
const files = this.discoverFiles(options);
|
|
88
|
+
// Phase 2: Analyze files
|
|
89
|
+
this.reportProgress({ phase: 'analyze', filesProcessed: 0, totalFiles: files.length });
|
|
90
|
+
const fileResults = await this.analyzeFiles(files, options);
|
|
91
|
+
// Phase 3: Build call graph
|
|
92
|
+
this.reportProgress({ phase: 'build-graph', filesProcessed: files.length, totalFiles: files.length });
|
|
93
|
+
this.buildCallGraph(fileResults, options);
|
|
94
|
+
// Phase 4: Detect dead code
|
|
95
|
+
this.reportProgress({ phase: 'detect', filesProcessed: files.length, totalFiles: files.length });
|
|
96
|
+
const deadCode = this.detectDeadCode(fileResults, options);
|
|
97
|
+
// Generate result
|
|
98
|
+
const result = this.generateResult(fileResults, deadCode, options, startTime);
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Discover files to analyze.
|
|
103
|
+
*/
|
|
104
|
+
discoverFiles(options) {
|
|
105
|
+
const { target, includeTests = false, includePatterns, excludePatterns } = options;
|
|
106
|
+
const files = [];
|
|
107
|
+
const defaultExcludes = [
|
|
108
|
+
'**/node_modules/**',
|
|
109
|
+
'**/vendor/**',
|
|
110
|
+
'**/target/**',
|
|
111
|
+
'**/build/**',
|
|
112
|
+
'**/dist/**',
|
|
113
|
+
'**/.git/**',
|
|
114
|
+
'**/.*',
|
|
115
|
+
];
|
|
116
|
+
if (!includeTests) {
|
|
117
|
+
defaultExcludes.push('**/test/**', '**/tests/**', '**/__tests__/**', '**/*.test.*', '**/*.spec.*', '**/*Test.java', '**/*Tests.java', '**/src/test/**');
|
|
118
|
+
}
|
|
119
|
+
const excludes = [...defaultExcludes, ...(excludePatterns || [])];
|
|
120
|
+
const languageExtensions = {
|
|
121
|
+
java: ['.java'],
|
|
122
|
+
c: ['.c', '.h'],
|
|
123
|
+
cpp: ['.cpp', '.cc', '.cxx', '.hpp', '.hxx'],
|
|
124
|
+
javascript: ['.js', '.jsx', '.mjs', '.cjs'],
|
|
125
|
+
typescript: ['.ts', '.tsx', '.mts', '.cts'],
|
|
126
|
+
python: ['.py'],
|
|
127
|
+
rust: ['.rs'],
|
|
128
|
+
};
|
|
129
|
+
const extensions = options.languages
|
|
130
|
+
? options.languages.flatMap(l => languageExtensions[l] || [])
|
|
131
|
+
: Object.values(languageExtensions).flat();
|
|
132
|
+
// Handle single file
|
|
133
|
+
if (fs.statSync(target).isFile()) {
|
|
134
|
+
return [path.resolve(target)];
|
|
135
|
+
}
|
|
136
|
+
// Walk directory
|
|
137
|
+
const walk = (dir, depth = 0) => {
|
|
138
|
+
if (depth > 30)
|
|
139
|
+
return;
|
|
140
|
+
try {
|
|
141
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
const fullPath = path.join(dir, entry.name);
|
|
144
|
+
const relativePath = path.relative(target, fullPath);
|
|
145
|
+
if (this.matchesPattern(relativePath, excludes)) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (entry.isDirectory()) {
|
|
149
|
+
walk(fullPath, depth + 1);
|
|
150
|
+
}
|
|
151
|
+
else if (entry.isFile()) {
|
|
152
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
153
|
+
if (!extensions.includes(ext))
|
|
154
|
+
continue;
|
|
155
|
+
if (includePatterns && includePatterns.length > 0) {
|
|
156
|
+
if (!this.matchesPattern(relativePath, includePatterns)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
files.push(fullPath);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Skip unreadable directories
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
walk(path.resolve(target));
|
|
169
|
+
if (this.verbose) {
|
|
170
|
+
console.log(`Discovered ${files.length} files to analyze`);
|
|
171
|
+
}
|
|
172
|
+
return files;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Check if path matches glob patterns.
|
|
176
|
+
*/
|
|
177
|
+
matchesPattern(filePath, patterns) {
|
|
178
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
179
|
+
for (const pattern of patterns) {
|
|
180
|
+
// Convert glob pattern to regex:
|
|
181
|
+
// 1. Escape regex special chars except glob chars (*, ?, [, ])
|
|
182
|
+
// 2. Convert glob patterns to regex equivalents
|
|
183
|
+
const regex = pattern
|
|
184
|
+
.replace(/\*\*/g, '{{DOUBLESTAR}}')
|
|
185
|
+
.replace(/\*/g, '{{STAR}}')
|
|
186
|
+
.replace(/\?/g, '{{QUESTION}}')
|
|
187
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape regex special chars
|
|
188
|
+
.replace(/{{DOUBLESTAR}}/g, '.*')
|
|
189
|
+
.replace(/{{STAR}}/g, '[^/]*')
|
|
190
|
+
.replace(/{{QUESTION}}/g, '.');
|
|
191
|
+
if (new RegExp(`^${regex}$`).test(normalized)) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Analyze files and extract methods.
|
|
199
|
+
*/
|
|
200
|
+
async analyzeFiles(files, options) {
|
|
201
|
+
const results = [];
|
|
202
|
+
let processed = 0;
|
|
203
|
+
for (const file of files) {
|
|
204
|
+
try {
|
|
205
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
206
|
+
const language = this.detectLanguage(file);
|
|
207
|
+
const ir = await analyze(content, path.basename(file), language);
|
|
208
|
+
results.push({
|
|
209
|
+
file: path.relative(options.target, file),
|
|
210
|
+
ir,
|
|
211
|
+
loc: ir.meta.loc,
|
|
212
|
+
});
|
|
213
|
+
processed++;
|
|
214
|
+
this.reportProgress({
|
|
215
|
+
phase: 'analyze',
|
|
216
|
+
filesProcessed: processed,
|
|
217
|
+
totalFiles: files.length,
|
|
218
|
+
message: path.basename(file),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
if (this.verbose) {
|
|
223
|
+
console.error(`Error analyzing ${file}: ${error}`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return results;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Build call graph from analyzed files.
|
|
231
|
+
*/
|
|
232
|
+
buildCallGraph(files, options) {
|
|
233
|
+
const { additionalEntryPoints = [], publicMethodsAsEntryPoints = false, includeTests = false } = options;
|
|
234
|
+
const additionalEntryPointSet = new Set(additionalEntryPoints);
|
|
235
|
+
// First pass: register all methods
|
|
236
|
+
for (const { file, ir } of files) {
|
|
237
|
+
const language = ir.meta.language;
|
|
238
|
+
for (const type of ir.types) {
|
|
239
|
+
if (type.kind !== 'class' && type.kind !== 'interface')
|
|
240
|
+
continue;
|
|
241
|
+
const classAnnotations = type.annotations || [];
|
|
242
|
+
const isEntryPointClass = classAnnotations.some(a => CLASS_ENTRY_POINT_ANNOTATIONS.has(a));
|
|
243
|
+
for (const method of type.methods) {
|
|
244
|
+
const methodId = this.getMethodId(file, type.name, method.name);
|
|
245
|
+
this.methodMap.set(methodId, {
|
|
246
|
+
id: methodId,
|
|
247
|
+
file,
|
|
248
|
+
className: type.name,
|
|
249
|
+
methodName: method.name,
|
|
250
|
+
startLine: method.start_line,
|
|
251
|
+
endLine: method.end_line,
|
|
252
|
+
lineCount: method.end_line - method.start_line + 1,
|
|
253
|
+
modifiers: method.modifiers,
|
|
254
|
+
annotations: method.annotations || [],
|
|
255
|
+
language,
|
|
256
|
+
});
|
|
257
|
+
this.callGraph.set(methodId, new Set());
|
|
258
|
+
this.reverseCallGraph.set(methodId, new Set());
|
|
259
|
+
// Check if this is an entry point
|
|
260
|
+
if (this.isEntryPoint(type, method, isEntryPointClass, additionalEntryPointSet, publicMethodsAsEntryPoints, includeTests, language)) {
|
|
261
|
+
this.entryPoints.add(methodId);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Track constructors (skip <module> in JS/TS/Python — it's not a real class)
|
|
265
|
+
if (type.kind === 'class' && !(this.isModuleBasedLanguage(language) && type.name === '<module>')) {
|
|
266
|
+
const constructorId = this.getMethodId(file, type.name, '<init>');
|
|
267
|
+
this.methodMap.set(constructorId, {
|
|
268
|
+
id: constructorId,
|
|
269
|
+
file,
|
|
270
|
+
className: type.name,
|
|
271
|
+
methodName: '<init>',
|
|
272
|
+
startLine: type.start_line,
|
|
273
|
+
endLine: type.end_line,
|
|
274
|
+
lineCount: type.end_line - type.start_line + 1,
|
|
275
|
+
modifiers: [],
|
|
276
|
+
annotations: [],
|
|
277
|
+
language,
|
|
278
|
+
});
|
|
279
|
+
this.callGraph.set(constructorId, new Set());
|
|
280
|
+
this.reverseCallGraph.set(constructorId, new Set());
|
|
281
|
+
// Constructors are entry points if class is instantiable from outside
|
|
282
|
+
if (isEntryPointClass || publicMethodsAsEntryPoints) {
|
|
283
|
+
this.entryPoints.add(constructorId);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Second pass: build call edges
|
|
289
|
+
for (const { file, ir } of files) {
|
|
290
|
+
for (const call of ir.calls) {
|
|
291
|
+
// Find the containing method
|
|
292
|
+
const containingMethod = this.findContainingMethod(file, ir, call.location.line);
|
|
293
|
+
if (!containingMethod)
|
|
294
|
+
continue;
|
|
295
|
+
// Resolve the target method
|
|
296
|
+
const targetMethods = this.resolveCallTarget(call, ir.types, files);
|
|
297
|
+
for (const targetId of targetMethods) {
|
|
298
|
+
// Add edge from caller to callee
|
|
299
|
+
this.callGraph.get(containingMethod)?.add(targetId);
|
|
300
|
+
// Add reverse edge
|
|
301
|
+
this.reverseCallGraph.get(targetId)?.add(containingMethod);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Third pass: JS/TS/Python module-level entry points
|
|
306
|
+
for (const { file, ir } of files) {
|
|
307
|
+
const language = ir.meta.language;
|
|
308
|
+
if (!this.isModuleBasedLanguage(language))
|
|
309
|
+
continue;
|
|
310
|
+
// Register <module-init> as synthetic entry point for top-level code
|
|
311
|
+
const moduleType = ir.types.find(t => t.name === '<module>');
|
|
312
|
+
if (!moduleType)
|
|
313
|
+
continue;
|
|
314
|
+
const moduleInitId = this.getMethodId(file, '<module>', '<module-init>');
|
|
315
|
+
this.methodMap.set(moduleInitId, {
|
|
316
|
+
id: moduleInitId,
|
|
317
|
+
file,
|
|
318
|
+
className: '<module>',
|
|
319
|
+
methodName: '<module-init>',
|
|
320
|
+
startLine: 1,
|
|
321
|
+
endLine: moduleType.end_line,
|
|
322
|
+
lineCount: 0, // synthetic, don't count LOC
|
|
323
|
+
modifiers: [],
|
|
324
|
+
annotations: [],
|
|
325
|
+
language,
|
|
326
|
+
});
|
|
327
|
+
this.callGraph.set(moduleInitId, new Set());
|
|
328
|
+
this.reverseCallGraph.set(moduleInitId, new Set());
|
|
329
|
+
this.entryPoints.add(moduleInitId);
|
|
330
|
+
// Assign orphan calls (top-level + anonymous callbacks) to <module-init>
|
|
331
|
+
for (const call of ir.calls) {
|
|
332
|
+
const containingMethod = this.findContainingMethod(file, ir, call.location.line);
|
|
333
|
+
if (containingMethod === null) {
|
|
334
|
+
const targetMethods = this.resolveCallTarget(call, ir.types, files);
|
|
335
|
+
for (const targetId of targetMethods) {
|
|
336
|
+
this.callGraph.get(moduleInitId)?.add(targetId);
|
|
337
|
+
this.reverseCallGraph.get(targetId)?.add(moduleInitId);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Check if a method is an entry point.
|
|
345
|
+
*/
|
|
346
|
+
isEntryPoint(type, method, isEntryPointClass, additionalEntryPoints, publicMethodsAsEntryPoints, includeTests, language) {
|
|
347
|
+
// JS/TS: entry points are determined by <module-init> reachability,
|
|
348
|
+
// NOT by method-level patterns. The third pass in buildCallGraph handles this.
|
|
349
|
+
// Here we only mark explicit framework patterns.
|
|
350
|
+
if (this.isJSTS(language)) {
|
|
351
|
+
// HTTP handler synthetic methods (get_handler, post_handler, etc.)
|
|
352
|
+
if (method.name.endsWith('_handler')) {
|
|
353
|
+
const verb = method.name.replace('_handler', '');
|
|
354
|
+
if (HTTP_VERB_METHODS.has(verb) || EVENT_LISTENER_METHODS.has(verb)) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Additional user-specified entry points
|
|
359
|
+
if (additionalEntryPoints.has(method.name)) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
// Don't auto-mark all <module> functions — let call graph decide
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
// Python: similar to JS/TS, module-level code is the entry point.
|
|
366
|
+
// Here we mark Flask/Django route handlers and other framework patterns.
|
|
367
|
+
if (this.isPython(language)) {
|
|
368
|
+
const annotations = method.annotations || [];
|
|
369
|
+
// Flask/Django route decorators
|
|
370
|
+
if (annotations.some(a => a.startsWith('route') || a.startsWith('app.route') ||
|
|
371
|
+
a === 'get' || a === 'post' || a === 'put' || a === 'delete' || a === 'patch')) {
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
// Django class-based views
|
|
375
|
+
if (annotations.includes('api_view') || annotations.includes('action')) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
// pytest/unittest markers
|
|
379
|
+
if (includeTests && annotations.some(a => a.startsWith('pytest') || a === 'test')) {
|
|
380
|
+
return true;
|
|
381
|
+
}
|
|
382
|
+
// Additional user-specified entry points
|
|
383
|
+
if (additionalEntryPoints.has(method.name)) {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
// Don't auto-mark all <module> functions — let call graph decide
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
// Rust: entry points include main, async runtime entry points, test functions, and pub fns
|
|
390
|
+
if (this.isRust(language)) {
|
|
391
|
+
const annotations = method.annotations || [];
|
|
392
|
+
// main function is always an entry point
|
|
393
|
+
if (method.name === 'main') {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
// Async runtime entry points
|
|
397
|
+
if (annotations.some(a => a.includes('tokio::main') || a.includes('tokio::test') ||
|
|
398
|
+
a.includes('actix_web::main') || a.includes('actix_rt::main') ||
|
|
399
|
+
a.includes('async_std::main') || a.includes('rocket::launch') ||
|
|
400
|
+
a === 'main' || a === 'launch')) {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
// Test functions
|
|
404
|
+
if (includeTests && (annotations.includes('test') ||
|
|
405
|
+
annotations.includes('tokio::test') ||
|
|
406
|
+
annotations.includes('async_std::test') ||
|
|
407
|
+
method.name.startsWith('test_'))) {
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
// Actix-web route handlers
|
|
411
|
+
if (annotations.some(a => a.includes('get') || a.includes('post') || a.includes('put') ||
|
|
412
|
+
a.includes('delete') || a.includes('patch') || a.includes('route'))) {
|
|
413
|
+
return true;
|
|
414
|
+
}
|
|
415
|
+
// Additional user-specified entry points
|
|
416
|
+
if (additionalEntryPoints.has(method.name)) {
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
// Public functions in lib.rs are potential entry points (library API)
|
|
420
|
+
if (method.modifiers.includes('pub') && publicMethodsAsEntryPoints) {
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
// Java logic
|
|
426
|
+
const methodAnnotations = method.annotations || [];
|
|
427
|
+
// Check explicit entry point methods
|
|
428
|
+
if (ENTRY_POINT_METHODS.has(method.name)) {
|
|
429
|
+
// For main, verify signature
|
|
430
|
+
if (method.name === 'main') {
|
|
431
|
+
const isPublic = method.modifiers.includes('public');
|
|
432
|
+
const isStatic = method.modifiers.includes('static');
|
|
433
|
+
if (isPublic && isStatic) {
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else if (!includeTests && ['setUp', 'tearDown', 'beforeEach', 'afterEach'].includes(method.name)) {
|
|
438
|
+
return false; // Skip test lifecycle methods if not including tests
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// Check annotations
|
|
445
|
+
for (const annotation of methodAnnotations) {
|
|
446
|
+
if (ENTRY_POINT_ANNOTATIONS.has(annotation)) {
|
|
447
|
+
if (!includeTests && ['Test', 'BeforeEach', 'AfterEach', 'BeforeAll', 'AfterAll', 'Before', 'After'].includes(annotation)) {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Check if class is an entry point class (all public methods are entry points)
|
|
454
|
+
if (isEntryPointClass && method.modifiers.includes('public')) {
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
// Check additional entry points
|
|
458
|
+
if (additionalEntryPoints.has(method.name)) {
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
// Check if public methods should be entry points
|
|
462
|
+
if (publicMethodsAsEntryPoints && method.modifiers.includes('public')) {
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Find the method containing a given line.
|
|
469
|
+
*/
|
|
470
|
+
findContainingMethod(file, ir, line) {
|
|
471
|
+
for (const type of ir.types) {
|
|
472
|
+
if (type.kind !== 'class' && type.kind !== 'interface')
|
|
473
|
+
continue;
|
|
474
|
+
for (const method of type.methods) {
|
|
475
|
+
if (line >= method.start_line && line <= method.end_line) {
|
|
476
|
+
return this.getMethodId(file, type.name, method.name);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Resolve a call to potential target methods.
|
|
484
|
+
*/
|
|
485
|
+
resolveCallTarget(call, types, allFiles) {
|
|
486
|
+
const targets = [];
|
|
487
|
+
const methodName = call.method_name;
|
|
488
|
+
const receiver = call.receiver;
|
|
489
|
+
// Simple resolution: find all methods with the same name
|
|
490
|
+
// In a real implementation, this would use type inference
|
|
491
|
+
for (const { file, ir } of allFiles) {
|
|
492
|
+
for (const type of ir.types) {
|
|
493
|
+
if (type.kind !== 'class' && type.kind !== 'interface')
|
|
494
|
+
continue;
|
|
495
|
+
for (const method of type.methods) {
|
|
496
|
+
if (method.name === methodName) {
|
|
497
|
+
// If we have a receiver, try to match the class
|
|
498
|
+
if (receiver) {
|
|
499
|
+
// Check if receiver matches class name (simplified)
|
|
500
|
+
const receiverLower = receiver.toLowerCase();
|
|
501
|
+
const classLower = type.name.toLowerCase();
|
|
502
|
+
if (receiverLower.includes(classLower) || classLower.includes(receiverLower)) {
|
|
503
|
+
targets.push(this.getMethodId(file, type.name, method.name));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
targets.push(this.getMethodId(file, type.name, method.name));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return targets;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Detect dead code using reachability analysis.
|
|
517
|
+
*/
|
|
518
|
+
detectDeadCode(files, options) {
|
|
519
|
+
const { maxDepth = 50 } = options;
|
|
520
|
+
const deadCode = [];
|
|
521
|
+
// Perform BFS from entry points
|
|
522
|
+
const queue = [];
|
|
523
|
+
for (const entryPoint of this.entryPoints) {
|
|
524
|
+
queue.push({ id: entryPoint, depth: 0 });
|
|
525
|
+
this.reachable.add(entryPoint);
|
|
526
|
+
}
|
|
527
|
+
while (queue.length > 0) {
|
|
528
|
+
const { id, depth } = queue.shift();
|
|
529
|
+
if (depth >= maxDepth)
|
|
530
|
+
continue;
|
|
531
|
+
const callees = this.callGraph.get(id);
|
|
532
|
+
if (callees) {
|
|
533
|
+
for (const callee of callees) {
|
|
534
|
+
if (!this.reachable.has(callee)) {
|
|
535
|
+
this.reachable.add(callee);
|
|
536
|
+
queue.push({ id: callee, depth: depth + 1 });
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Find unreachable methods
|
|
542
|
+
for (const [methodId, node] of this.methodMap) {
|
|
543
|
+
if (!this.reachable.has(methodId)) {
|
|
544
|
+
// Determine confidence
|
|
545
|
+
let confidence = 'high';
|
|
546
|
+
let reason = 'Not reachable from any entry point';
|
|
547
|
+
if (this.isJSTS(node.language)) {
|
|
548
|
+
// Skip synthetic <module-init> — not real dead code
|
|
549
|
+
if (node.methodName === '<module-init>')
|
|
550
|
+
continue;
|
|
551
|
+
// Underscore-prefixed = conventionally private
|
|
552
|
+
if (node.methodName.startsWith('_')) {
|
|
553
|
+
confidence = 'high';
|
|
554
|
+
reason = 'Private helper function not called from any entry point';
|
|
555
|
+
}
|
|
556
|
+
// Callback handler names — may be invoked by framework
|
|
557
|
+
else if (node.methodName.endsWith('_handler')) {
|
|
558
|
+
confidence = 'low';
|
|
559
|
+
reason = 'Callback handler (may be invoked by framework)';
|
|
560
|
+
}
|
|
561
|
+
// JS protocol methods
|
|
562
|
+
else if (['toString', 'valueOf', 'toJSON', 'constructor'].includes(node.methodName)) {
|
|
563
|
+
confidence = 'low';
|
|
564
|
+
reason = 'Standard protocol method (may be called implicitly)';
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
else if (this.isPython(node.language)) {
|
|
568
|
+
// Skip synthetic <module-init> — not real dead code
|
|
569
|
+
if (node.methodName === '<module-init>')
|
|
570
|
+
continue;
|
|
571
|
+
// Single underscore prefix = conventionally private
|
|
572
|
+
if (node.methodName.startsWith('_') && !node.methodName.startsWith('__')) {
|
|
573
|
+
confidence = 'high';
|
|
574
|
+
reason = 'Private helper function not called from any entry point';
|
|
575
|
+
}
|
|
576
|
+
// Dunder methods = special/magic methods, may be called implicitly
|
|
577
|
+
else if (node.methodName.startsWith('__') && node.methodName.endsWith('__')) {
|
|
578
|
+
confidence = 'low';
|
|
579
|
+
reason = 'Magic method (may be called implicitly by Python runtime)';
|
|
580
|
+
}
|
|
581
|
+
// Test methods (if not including tests)
|
|
582
|
+
else if (node.methodName.startsWith('test_')) {
|
|
583
|
+
confidence = 'low';
|
|
584
|
+
reason = 'Test method (may be called by test runner)';
|
|
585
|
+
}
|
|
586
|
+
// Common callback/handler patterns
|
|
587
|
+
else if (node.methodName.endsWith('_handler') || node.methodName.endsWith('_callback')) {
|
|
588
|
+
confidence = 'low';
|
|
589
|
+
reason = 'Callback/handler (may be invoked by framework)';
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
else if (this.isRust(node.language)) {
|
|
593
|
+
// Rust-specific confidence logic
|
|
594
|
+
// Test functions
|
|
595
|
+
if (node.methodName.startsWith('test_') || node.annotations.some(a => a.includes('test'))) {
|
|
596
|
+
confidence = 'low';
|
|
597
|
+
reason = 'Test function (may be called by test runner)';
|
|
598
|
+
}
|
|
599
|
+
// Trait implementations often called implicitly
|
|
600
|
+
else if (['new', 'default', 'from', 'into', 'clone', 'drop', 'deref'].includes(node.methodName)) {
|
|
601
|
+
confidence = 'low';
|
|
602
|
+
reason = 'Trait method (may be called implicitly)';
|
|
603
|
+
}
|
|
604
|
+
// Private functions (not pub)
|
|
605
|
+
else if (!node.modifiers.includes('pub')) {
|
|
606
|
+
confidence = 'high';
|
|
607
|
+
reason = 'Private function not called from any entry point';
|
|
608
|
+
}
|
|
609
|
+
// Public functions in library crates may be external API
|
|
610
|
+
else if (node.modifiers.includes('pub')) {
|
|
611
|
+
confidence = 'medium';
|
|
612
|
+
reason = 'Public function (may be library API)';
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
// Java confidence logic
|
|
617
|
+
// Lower confidence for private methods (may be used via reflection)
|
|
618
|
+
if (node.modifiers.includes('private')) {
|
|
619
|
+
confidence = 'medium';
|
|
620
|
+
reason = 'Private method not called directly (could be reflection)';
|
|
621
|
+
}
|
|
622
|
+
// Lower confidence for methods with certain names
|
|
623
|
+
if (['equals', 'hashCode', 'toString', 'compareTo', 'clone'].includes(node.methodName)) {
|
|
624
|
+
confidence = 'low';
|
|
625
|
+
reason = 'Standard method override (may be called implicitly)';
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
deadCode.push({
|
|
629
|
+
type: 'method',
|
|
630
|
+
file: node.file,
|
|
631
|
+
name: node.methodName,
|
|
632
|
+
className: node.className,
|
|
633
|
+
startLine: node.startLine,
|
|
634
|
+
endLine: node.endLine,
|
|
635
|
+
lineCount: node.lineCount,
|
|
636
|
+
reason,
|
|
637
|
+
confidence,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
// Sort by file and line
|
|
642
|
+
deadCode.sort((a, b) => {
|
|
643
|
+
if (a.file !== b.file)
|
|
644
|
+
return a.file.localeCompare(b.file);
|
|
645
|
+
return a.startLine - b.startLine;
|
|
646
|
+
});
|
|
647
|
+
return deadCode;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Generate final result.
|
|
651
|
+
*/
|
|
652
|
+
generateResult(files, deadCode, options, startTime) {
|
|
653
|
+
// Calculate summary
|
|
654
|
+
let totalLOC = 0;
|
|
655
|
+
let totalMethods = 0;
|
|
656
|
+
let totalClasses = 0;
|
|
657
|
+
let deadLOC = 0;
|
|
658
|
+
for (const { ir, loc } of files) {
|
|
659
|
+
totalLOC += loc;
|
|
660
|
+
for (const type of ir.types) {
|
|
661
|
+
if (type.kind === 'class')
|
|
662
|
+
totalClasses++;
|
|
663
|
+
totalMethods += type.methods.length;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const byFile = new Map();
|
|
667
|
+
for (const item of deadCode) {
|
|
668
|
+
deadLOC += item.lineCount;
|
|
669
|
+
if (!byFile.has(item.file)) {
|
|
670
|
+
byFile.set(item.file, { deadMethods: 0, deadClasses: 0, deadLOC: 0 });
|
|
671
|
+
}
|
|
672
|
+
const fileStats = byFile.get(item.file);
|
|
673
|
+
if (item.type === 'method')
|
|
674
|
+
fileStats.deadMethods++;
|
|
675
|
+
if (item.type === 'class')
|
|
676
|
+
fileStats.deadClasses++;
|
|
677
|
+
fileStats.deadLOC += item.lineCount;
|
|
678
|
+
}
|
|
679
|
+
// Generate entry point list
|
|
680
|
+
const entryPointsList = [];
|
|
681
|
+
for (const entryPointId of this.entryPoints) {
|
|
682
|
+
const node = this.methodMap.get(entryPointId);
|
|
683
|
+
if (node) {
|
|
684
|
+
entryPointsList.push({
|
|
685
|
+
type: this.getEntryPointType(node),
|
|
686
|
+
file: node.file,
|
|
687
|
+
className: node.className,
|
|
688
|
+
methodName: node.methodName,
|
|
689
|
+
line: node.startLine,
|
|
690
|
+
reason: this.getEntryPointReason(node),
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
meta: {
|
|
696
|
+
target: options.target,
|
|
697
|
+
timestamp: new Date().toISOString(),
|
|
698
|
+
durationMs: Date.now() - startTime,
|
|
699
|
+
filesAnalyzed: files.length,
|
|
700
|
+
totalMethods,
|
|
701
|
+
totalClasses,
|
|
702
|
+
},
|
|
703
|
+
entryPoints: entryPointsList,
|
|
704
|
+
deadCode,
|
|
705
|
+
summary: {
|
|
706
|
+
totalFiles: files.length,
|
|
707
|
+
totalLOC,
|
|
708
|
+
totalMethods,
|
|
709
|
+
totalClasses,
|
|
710
|
+
deadMethods: deadCode.filter(d => d.type === 'method').length,
|
|
711
|
+
deadClasses: deadCode.filter(d => d.type === 'class').length,
|
|
712
|
+
deadLOC,
|
|
713
|
+
deadPercentage: totalLOC > 0 ? Math.round((deadLOC / totalLOC) * 1000) / 10 : 0,
|
|
714
|
+
byFile: Array.from(byFile.entries()).map(([file, stats]) => ({ file, ...stats })),
|
|
715
|
+
},
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Get entry point type.
|
|
720
|
+
*/
|
|
721
|
+
getEntryPointType(node) {
|
|
722
|
+
if (this.isJSTS(node.language)) {
|
|
723
|
+
if (node.methodName === '<module-init>')
|
|
724
|
+
return 'export';
|
|
725
|
+
if (node.methodName.endsWith('_handler'))
|
|
726
|
+
return 'handler';
|
|
727
|
+
return 'export';
|
|
728
|
+
}
|
|
729
|
+
if (this.isPython(node.language)) {
|
|
730
|
+
if (node.methodName === '<module-init>')
|
|
731
|
+
return 'export';
|
|
732
|
+
// Route decorators
|
|
733
|
+
if (node.annotations.some(a => a.startsWith('route') || a.startsWith('app.route') ||
|
|
734
|
+
a === 'get' || a === 'post' || a === 'put' || a === 'delete')) {
|
|
735
|
+
return 'handler';
|
|
736
|
+
}
|
|
737
|
+
return 'export';
|
|
738
|
+
}
|
|
739
|
+
if (this.isRust(node.language)) {
|
|
740
|
+
if (node.methodName === 'main')
|
|
741
|
+
return 'main';
|
|
742
|
+
// Async runtime entry points
|
|
743
|
+
if (node.annotations.some(a => a.includes('tokio::main') || a.includes('actix_web::main') ||
|
|
744
|
+
a.includes('rocket::launch'))) {
|
|
745
|
+
return 'main';
|
|
746
|
+
}
|
|
747
|
+
// Route handlers
|
|
748
|
+
if (node.annotations.some(a => a.includes('get') || a.includes('post') || a.includes('put') ||
|
|
749
|
+
a.includes('delete') || a.includes('route'))) {
|
|
750
|
+
return 'handler';
|
|
751
|
+
}
|
|
752
|
+
// Test functions
|
|
753
|
+
if (node.annotations.some(a => a.includes('test'))) {
|
|
754
|
+
return 'annotation';
|
|
755
|
+
}
|
|
756
|
+
// Public functions in lib
|
|
757
|
+
if (node.modifiers.includes('pub')) {
|
|
758
|
+
return 'export';
|
|
759
|
+
}
|
|
760
|
+
return 'public';
|
|
761
|
+
}
|
|
762
|
+
if (node.methodName === 'main')
|
|
763
|
+
return 'main';
|
|
764
|
+
if (node.methodName === '<init>')
|
|
765
|
+
return 'constructor';
|
|
766
|
+
const handlerAnnotations = ['GetMapping', 'PostMapping', 'PutMapping', 'DeleteMapping', 'RequestMapping', 'GET', 'POST', 'PUT', 'DELETE'];
|
|
767
|
+
if (node.annotations.some(a => handlerAnnotations.includes(a)))
|
|
768
|
+
return 'handler';
|
|
769
|
+
if (node.annotations.some(a => ENTRY_POINT_ANNOTATIONS.has(a)))
|
|
770
|
+
return 'annotation';
|
|
771
|
+
return 'public';
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Get entry point reason.
|
|
775
|
+
*/
|
|
776
|
+
getEntryPointReason(node) {
|
|
777
|
+
if (this.isJSTS(node.language)) {
|
|
778
|
+
if (node.methodName === '<module-init>')
|
|
779
|
+
return 'Module top-level code';
|
|
780
|
+
if (node.methodName.endsWith('_handler')) {
|
|
781
|
+
const verb = node.methodName.replace('_handler', '').toUpperCase();
|
|
782
|
+
return `HTTP route handler (${verb})`;
|
|
783
|
+
}
|
|
784
|
+
return 'Exported function';
|
|
785
|
+
}
|
|
786
|
+
if (this.isPython(node.language)) {
|
|
787
|
+
if (node.methodName === '<module-init>')
|
|
788
|
+
return 'Module top-level code';
|
|
789
|
+
// Route decorators
|
|
790
|
+
if (node.annotations.some(a => a.startsWith('route') || a.startsWith('app.route'))) {
|
|
791
|
+
return 'Flask/Django route handler';
|
|
792
|
+
}
|
|
793
|
+
if (node.annotations.some(a => a === 'get' || a === 'post' || a === 'put' || a === 'delete')) {
|
|
794
|
+
return 'HTTP route handler';
|
|
795
|
+
}
|
|
796
|
+
return 'Exported function';
|
|
797
|
+
}
|
|
798
|
+
if (this.isRust(node.language)) {
|
|
799
|
+
if (node.methodName === 'main')
|
|
800
|
+
return 'Main function';
|
|
801
|
+
// Async runtime entry points
|
|
802
|
+
if (node.annotations.some(a => a.includes('tokio::main'))) {
|
|
803
|
+
return 'Tokio async main';
|
|
804
|
+
}
|
|
805
|
+
if (node.annotations.some(a => a.includes('actix_web::main') || a.includes('actix_rt::main'))) {
|
|
806
|
+
return 'Actix-web main';
|
|
807
|
+
}
|
|
808
|
+
if (node.annotations.some(a => a.includes('rocket::launch'))) {
|
|
809
|
+
return 'Rocket launch';
|
|
810
|
+
}
|
|
811
|
+
// Route handlers
|
|
812
|
+
if (node.annotations.some(a => a.includes('get')))
|
|
813
|
+
return 'HTTP GET handler';
|
|
814
|
+
if (node.annotations.some(a => a.includes('post')))
|
|
815
|
+
return 'HTTP POST handler';
|
|
816
|
+
if (node.annotations.some(a => a.includes('put')))
|
|
817
|
+
return 'HTTP PUT handler';
|
|
818
|
+
if (node.annotations.some(a => a.includes('delete')))
|
|
819
|
+
return 'HTTP DELETE handler';
|
|
820
|
+
if (node.annotations.some(a => a.includes('route')))
|
|
821
|
+
return 'HTTP route handler';
|
|
822
|
+
// Test functions
|
|
823
|
+
if (node.annotations.some(a => a.includes('test'))) {
|
|
824
|
+
return 'Test function';
|
|
825
|
+
}
|
|
826
|
+
// Public functions
|
|
827
|
+
if (node.modifiers.includes('pub')) {
|
|
828
|
+
return 'Public API function';
|
|
829
|
+
}
|
|
830
|
+
return 'Entry point';
|
|
831
|
+
}
|
|
832
|
+
if (node.methodName === 'main')
|
|
833
|
+
return 'Main method';
|
|
834
|
+
if (node.methodName === '<init>')
|
|
835
|
+
return 'Constructor';
|
|
836
|
+
const handlerAnnotations = ['GetMapping', 'PostMapping', 'PutMapping', 'DeleteMapping', 'RequestMapping'];
|
|
837
|
+
const handler = node.annotations.find(a => handlerAnnotations.includes(a));
|
|
838
|
+
if (handler)
|
|
839
|
+
return `HTTP handler (@${handler})`;
|
|
840
|
+
const annotation = node.annotations.find(a => ENTRY_POINT_ANNOTATIONS.has(a));
|
|
841
|
+
if (annotation)
|
|
842
|
+
return `Annotated with @${annotation}`;
|
|
843
|
+
return 'Public method';
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Generate method ID.
|
|
847
|
+
*/
|
|
848
|
+
getMethodId(file, className, methodName) {
|
|
849
|
+
return `${file}::${className}::${methodName}`;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Detect language from file extension.
|
|
853
|
+
*/
|
|
854
|
+
detectLanguage(file) {
|
|
855
|
+
const ext = path.extname(file).toLowerCase();
|
|
856
|
+
switch (ext) {
|
|
857
|
+
case '.java': return 'java';
|
|
858
|
+
case '.c':
|
|
859
|
+
case '.h': return 'c';
|
|
860
|
+
case '.cpp':
|
|
861
|
+
case '.cc':
|
|
862
|
+
case '.cxx':
|
|
863
|
+
case '.hpp': return 'cpp';
|
|
864
|
+
case '.js':
|
|
865
|
+
case '.jsx':
|
|
866
|
+
case '.mjs':
|
|
867
|
+
case '.cjs': return 'javascript';
|
|
868
|
+
case '.ts':
|
|
869
|
+
case '.tsx':
|
|
870
|
+
case '.mts':
|
|
871
|
+
case '.cts': return 'typescript';
|
|
872
|
+
case '.py': return 'python';
|
|
873
|
+
case '.rs': return 'rust';
|
|
874
|
+
default: return 'java';
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Check if language is JavaScript or TypeScript.
|
|
879
|
+
*/
|
|
880
|
+
isJSTS(lang) {
|
|
881
|
+
return lang === 'javascript' || lang === 'typescript';
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Check if language is Python.
|
|
885
|
+
*/
|
|
886
|
+
isPython(lang) {
|
|
887
|
+
return lang === 'python';
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Check if language is Rust.
|
|
891
|
+
*/
|
|
892
|
+
isRust(lang) {
|
|
893
|
+
return lang === 'rust';
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Check if language uses module-level entry points (JS/TS/Python).
|
|
897
|
+
*/
|
|
898
|
+
isModuleBasedLanguage(lang) {
|
|
899
|
+
return this.isJSTS(lang) || this.isPython(lang);
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Report progress.
|
|
903
|
+
*/
|
|
904
|
+
reportProgress(progress) {
|
|
905
|
+
if (this.onProgress) {
|
|
906
|
+
this.onProgress(progress);
|
|
907
|
+
}
|
|
908
|
+
if (this.verbose) {
|
|
909
|
+
console.log(`[${progress.phase}] ${progress.filesProcessed}/${progress.totalFiles} ${progress.message || ''}`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
// ============================================================================
|
|
914
|
+
// Convenience Functions
|
|
915
|
+
// ============================================================================
|
|
916
|
+
/**
|
|
917
|
+
* Detect dead code in a directory or file.
|
|
918
|
+
*/
|
|
919
|
+
export async function detectDeadCode(options) {
|
|
920
|
+
const detector = new DeadCodeDetector();
|
|
921
|
+
return detector.detect(options);
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Quick dead code detection with default options.
|
|
925
|
+
*/
|
|
926
|
+
export async function quickDeadCodeCheck(target) {
|
|
927
|
+
return detectDeadCode({
|
|
928
|
+
target,
|
|
929
|
+
verbose: false,
|
|
930
|
+
publicMethodsAsEntryPoints: false,
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Generate a text report from dead code results.
|
|
935
|
+
*/
|
|
936
|
+
export function formatDeadCodeReport(result) {
|
|
937
|
+
const lines = [
|
|
938
|
+
'='.repeat(70),
|
|
939
|
+
'DEAD CODE ANALYSIS REPORT',
|
|
940
|
+
'='.repeat(70),
|
|
941
|
+
'',
|
|
942
|
+
`Target: ${result.meta.target}`,
|
|
943
|
+
`Timestamp: ${result.meta.timestamp}`,
|
|
944
|
+
`Duration: ${(result.meta.durationMs / 1000).toFixed(2)}s`,
|
|
945
|
+
'',
|
|
946
|
+
'-'.repeat(70),
|
|
947
|
+
'SUMMARY',
|
|
948
|
+
'-'.repeat(70),
|
|
949
|
+
'',
|
|
950
|
+
`Files Analyzed: ${result.summary.totalFiles}`,
|
|
951
|
+
`Total LOC: ${result.summary.totalLOC.toLocaleString()}`,
|
|
952
|
+
`Total Methods: ${result.summary.totalMethods}`,
|
|
953
|
+
`Total Classes: ${result.summary.totalClasses}`,
|
|
954
|
+
'',
|
|
955
|
+
`Dead Methods: ${result.summary.deadMethods}`,
|
|
956
|
+
`Dead Classes: ${result.summary.deadClasses}`,
|
|
957
|
+
`Dead LOC: ${result.summary.deadLOC.toLocaleString()} (${result.summary.deadPercentage}%)`,
|
|
958
|
+
'',
|
|
959
|
+
];
|
|
960
|
+
if (result.entryPoints.length > 0) {
|
|
961
|
+
lines.push('-'.repeat(70));
|
|
962
|
+
lines.push(`ENTRY POINTS (${result.entryPoints.length})`);
|
|
963
|
+
lines.push('-'.repeat(70));
|
|
964
|
+
lines.push('');
|
|
965
|
+
for (const ep of result.entryPoints.slice(0, 20)) {
|
|
966
|
+
const location = ep.className ? `${ep.className}.${ep.methodName}` : ep.methodName;
|
|
967
|
+
lines.push(` ${ep.type.toUpperCase().padEnd(12)} ${ep.file}:${ep.line} - ${location}`);
|
|
968
|
+
}
|
|
969
|
+
if (result.entryPoints.length > 20) {
|
|
970
|
+
lines.push(` ... and ${result.entryPoints.length - 20} more`);
|
|
971
|
+
}
|
|
972
|
+
lines.push('');
|
|
973
|
+
}
|
|
974
|
+
if (result.deadCode.length > 0) {
|
|
975
|
+
lines.push('-'.repeat(70));
|
|
976
|
+
lines.push(`DEAD CODE (${result.deadCode.length} items)`);
|
|
977
|
+
lines.push('-'.repeat(70));
|
|
978
|
+
lines.push('');
|
|
979
|
+
// Group by file
|
|
980
|
+
const byFile = new Map();
|
|
981
|
+
for (const item of result.deadCode) {
|
|
982
|
+
if (!byFile.has(item.file)) {
|
|
983
|
+
byFile.set(item.file, []);
|
|
984
|
+
}
|
|
985
|
+
byFile.get(item.file).push(item);
|
|
986
|
+
}
|
|
987
|
+
for (const [file, items] of byFile) {
|
|
988
|
+
lines.push(`${file} (${items.length} items, ${items.reduce((sum, i) => sum + i.lineCount, 0)} lines):`);
|
|
989
|
+
for (const item of items.slice(0, 10)) {
|
|
990
|
+
const name = item.className ? `${item.className}.${item.name}` : item.name;
|
|
991
|
+
const conf = item.confidence === 'high' ? '' : ` [${item.confidence}]`;
|
|
992
|
+
lines.push(` L${item.startLine}-${item.endLine}: ${item.type} ${name}${conf}`);
|
|
993
|
+
}
|
|
994
|
+
if (items.length > 10) {
|
|
995
|
+
lines.push(` ... and ${items.length - 10} more`);
|
|
996
|
+
}
|
|
997
|
+
lines.push('');
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
lines.push('='.repeat(70));
|
|
1001
|
+
return lines.join('\n');
|
|
1002
|
+
}
|
|
1003
|
+
//# sourceMappingURL=detector.js.map
|