@crownpeak/dqm-react-component-dev-mcp 1.2.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/README.md +138 -0
- package/data/.env.example +22 -0
- package/data/.gitattributes +47 -0
- package/data/.glfrc.json +7 -0
- package/data/.husky/pre-commit +5 -0
- package/data/.nvmrc +1 -0
- package/data/CHANGELOG.md +75 -0
- package/data/CODE_OF_CONDUCT.md +129 -0
- package/data/CONTRIBUTING.md +203 -0
- package/data/DOCS-STRUCTURE.md +307 -0
- package/data/I18N.md +292 -0
- package/data/LICENSE +22 -0
- package/data/README.md +315 -0
- package/data/SECURITY.md +125 -0
- package/data/WIKI-DEPLOYMENT.md +348 -0
- package/data/docs/AI-FEATURES.md +610 -0
- package/data/docs/API-REFERENCE.md +1022 -0
- package/data/docs/AUTHENTICATION.md +301 -0
- package/data/docs/BACKEND-API.md +468 -0
- package/data/docs/DEVELOPMENT.md +375 -0
- package/data/docs/EXAMPLES.md +622 -0
- package/data/docs/MCP-SERVER.md +307 -0
- package/data/docs/MIGRATION-GUIDE.md +367 -0
- package/data/docs/NPM-PUBLISH.md +193 -0
- package/data/docs/QUICKSTART.md +206 -0
- package/data/docs/REDIS-SETUP.md +162 -0
- package/data/docs/SERVER.md +228 -0
- package/data/docs/TROUBLESHOOTING.md +657 -0
- package/data/docs/WIDGET-GUIDE.md +638 -0
- package/data/docs/WIKI-HOME.md +58 -0
- package/data/docs/WIKI-SIDEBAR.md +39 -0
- package/data/package.json +171 -0
- package/data/playwright.config.ts +64 -0
- package/data/probe/.cargo/config.toml +10 -0
- package/data/probe/.claude/commands/performance-review.md +15 -0
- package/data/probe/.clinerules +288 -0
- package/data/probe/.dockerignore +57 -0
- package/data/probe/.githooks/post-commit +11 -0
- package/data/probe/.githooks/pre-commit +99 -0
- package/data/probe/.githooks/pre-commit-vow +9 -0
- package/data/probe/.prompts/engineer.md +41 -0
- package/data/probe/.roomodes +28 -0
- package/data/probe/.windsurfrules +0 -0
- package/data/probe/BASH_TOOL_SUMMARY.md +148 -0
- package/data/probe/BENCHMARKING.md +256 -0
- package/data/probe/CLAUDE.md +226 -0
- package/data/probe/CODE_OF_CONDUCT.md +128 -0
- package/data/probe/CONTRIBUTING.md +193 -0
- package/data/probe/Cargo.toml +120 -0
- package/data/probe/Cross.toml +10 -0
- package/data/probe/DOCKER-README.md +224 -0
- package/data/probe/Dockerfile +32 -0
- package/data/probe/ENHANCED_DEBUG_TELEMETRY.md +188 -0
- package/data/probe/LICENSE +201 -0
- package/data/probe/Makefile +210 -0
- package/data/probe/README.md +824 -0
- package/data/probe/SECURITY.md +67 -0
- package/data/probe/WINDOWS-GUIDE.md +294 -0
- package/data/probe/benches/parsing_benchmarks.rs +370 -0
- package/data/probe/benches/search_benchmarks.rs +599 -0
- package/data/probe/benches/simd_benchmarks.rs +372 -0
- package/data/probe/benches/timing_benchmarks.rs +287 -0
- package/data/probe/build-windows.bat +229 -0
- package/data/probe/codex-config/config.toml +6 -0
- package/data/probe/docs/PERFORMANCE_OPTIMIZATION.md +161 -0
- package/data/probe/examples/cache_demo.rs +46 -0
- package/data/probe/examples/chat/.dockerignore +37 -0
- package/data/probe/examples/chat/ChatSessionManager.js +295 -0
- package/data/probe/examples/chat/Dockerfile +98 -0
- package/data/probe/examples/chat/LICENSE +201 -0
- package/data/probe/examples/chat/LOCAL_IMAGE_SUPPORT.md +195 -0
- package/data/probe/examples/chat/MCP_INTEGRATION.md +400 -0
- package/data/probe/examples/chat/README.md +338 -0
- package/data/probe/examples/chat/TRACING.md +226 -0
- package/data/probe/examples/chat/appTracer.js +968 -0
- package/data/probe/examples/chat/auth.js +76 -0
- package/data/probe/examples/chat/bin/probe-chat.js +13 -0
- package/data/probe/examples/chat/build.js +104 -0
- package/data/probe/examples/chat/cancelRequest.js +84 -0
- package/data/probe/examples/chat/demo-agentic-image-flow.js +88 -0
- package/data/probe/examples/chat/demo-local-images.js +128 -0
- package/data/probe/examples/chat/fileSpanExporter.js +181 -0
- package/data/probe/examples/chat/implement/README.md +228 -0
- package/data/probe/examples/chat/implement/backends/AiderBackend.js +750 -0
- package/data/probe/examples/chat/implement/backends/BaseBackend.js +276 -0
- package/data/probe/examples/chat/implement/backends/ClaudeCodeBackend.js +767 -0
- package/data/probe/examples/chat/implement/backends/MockBackend.js +237 -0
- package/data/probe/examples/chat/implement/backends/registry.js +85 -0
- package/data/probe/examples/chat/implement/core/BackendManager.js +567 -0
- package/data/probe/examples/chat/implement/core/ImplementTool.js +354 -0
- package/data/probe/examples/chat/implement/core/config.js +428 -0
- package/data/probe/examples/chat/implement/core/timeouts.js +58 -0
- package/data/probe/examples/chat/implement/core/utils.js +496 -0
- package/data/probe/examples/chat/implement/types/BackendTypes.js +126 -0
- package/data/probe/examples/chat/index.js +669 -0
- package/data/probe/examples/chat/mcpServer.js +341 -0
- package/data/probe/examples/chat/npm/LICENSE +15 -0
- package/data/probe/examples/chat/npm/README.md +168 -0
- package/data/probe/examples/chat/npm/bin/probe-chat.js +156 -0
- package/data/probe/examples/chat/npm/index.js +259 -0
- package/data/probe/examples/chat/npm/package.json +54 -0
- package/data/probe/examples/chat/package.json +102 -0
- package/data/probe/examples/chat/probeChat.js +456 -0
- package/data/probe/examples/chat/probeTool.js +491 -0
- package/data/probe/examples/chat/storage/JsonChatStorage.js +476 -0
- package/data/probe/examples/chat/telemetry.js +281 -0
- package/data/probe/examples/chat/test/integration/chatFlows.test.js +320 -0
- package/data/probe/examples/chat/test/integration/toolCalling.test.js +471 -0
- package/data/probe/examples/chat/test/mocks/mockLLMProvider.js +269 -0
- package/data/probe/examples/chat/test/test-backends.js +90 -0
- package/data/probe/examples/chat/test/testUtils.js +530 -0
- package/data/probe/examples/chat/test/unit/backendTimeout.test.js +161 -0
- package/data/probe/examples/chat/test/unit/packageFiles.test.js +120 -0
- package/data/probe/examples/chat/test/verify-tests.js +118 -0
- package/data/probe/examples/chat/test-agentic-image-loading.js +294 -0
- package/data/probe/examples/chat/test-ai-sdk-telemetry.js +204 -0
- package/data/probe/examples/chat/test-chat-tracing.js +38 -0
- package/data/probe/examples/chat/test-direct-function.js +49 -0
- package/data/probe/examples/chat/test-file-size-validation.js +103 -0
- package/data/probe/examples/chat/test-full-mcp-integration.js +258 -0
- package/data/probe/examples/chat/test-github-context.txt +12 -0
- package/data/probe/examples/chat/test-hierarchy.js +203 -0
- package/data/probe/examples/chat/test-image-spans.js +37 -0
- package/data/probe/examples/chat/test-local-image-reading.js +176 -0
- package/data/probe/examples/chat/test-mcp-integration.js +136 -0
- package/data/probe/examples/chat/test-mcp-probe-server.js +161 -0
- package/data/probe/examples/chat/test-mcp-with-ai.js +279 -0
- package/data/probe/examples/chat/test-multiple-allowed-dirs.js +111 -0
- package/data/probe/examples/chat/test-probe-mcp-server.js +110 -0
- package/data/probe/examples/chat/test-security-validation.js +145 -0
- package/data/probe/examples/chat/test-simple-tracing.js +32 -0
- package/data/probe/examples/chat/test-trace-verification.js +235 -0
- package/data/probe/examples/chat/test-tracing.js +114 -0
- package/data/probe/examples/chat/tokenCounter.js +419 -0
- package/data/probe/examples/chat/tokenUsageDisplay.js +134 -0
- package/data/probe/examples/chat/webServer.js +1103 -0
- package/data/probe/examples/reranker/Cargo.toml +33 -0
- package/data/probe/examples/reranker/DEBUG_OUTPUT_ANALYSIS.md +71 -0
- package/data/probe/examples/reranker/MODELS.md +66 -0
- package/data/probe/examples/reranker/MODEL_COMPARISON.md +60 -0
- package/data/probe/examples/reranker/MULTI_MODEL_ANALYSIS.md +176 -0
- package/data/probe/examples/reranker/PERFORMANCE_SUMMARY.md +156 -0
- package/data/probe/examples/reranker/README.md +347 -0
- package/data/probe/examples/reranker/RUST_BERT_COMPARISON.md +82 -0
- package/data/probe/examples/reranker/TOKENIZATION_GUIDE.md +120 -0
- package/data/probe/examples/reranker/check_rust_tokenizer.py +108 -0
- package/data/probe/examples/reranker/convert_to_torchscript.py +109 -0
- package/data/probe/examples/reranker/debug_scoring.py +189 -0
- package/data/probe/examples/reranker/debug_tokenization.py +154 -0
- package/data/probe/examples/reranker/download_models.sh +73 -0
- package/data/probe/examples/reranker/requirements.txt +13 -0
- package/data/probe/examples/reranker/run_comprehensive_benchmark.sh +83 -0
- package/data/probe/examples/reranker/rust_bert_test/Cargo.toml +12 -0
- package/data/probe/examples/reranker/rust_bert_test/README.md +54 -0
- package/data/probe/examples/reranker/simple_test.py +50 -0
- package/data/probe/examples/reranker/test_all_models.sh +63 -0
- package/data/probe/examples/reranker/test_bert_results.sh +44 -0
- package/data/probe/examples/reranker/test_cross_encoder.py +334 -0
- package/data/probe/examples/reranker/test_cross_encoder.sh +80 -0
- package/data/probe/examples/reranker/test_exact_comparison.py +151 -0
- package/data/probe/examples/reranker/test_parallel_performance.sh +56 -0
- package/data/probe/examples/reranker/test_scores.py +132 -0
- package/data/probe/install.ps1 +508 -0
- package/data/probe/install.sh +460 -0
- package/data/probe/npm/CLONE_METHOD_EXAMPLES.md +596 -0
- package/data/probe/npm/CONTEXT_COMPACTION.md +303 -0
- package/data/probe/npm/DELEGATE_TOOL_README.md +166 -0
- package/data/probe/npm/MAID_INTEGRATION.md +313 -0
- package/data/probe/npm/MCP_INTEGRATION_SUMMARY.md +241 -0
- package/data/probe/npm/README.md +824 -0
- package/data/probe/npm/bin/.gitignore +7 -0
- package/data/probe/npm/bin/.gitkeep +0 -0
- package/data/probe/npm/bin/README.md +12 -0
- package/data/probe/npm/bin/probe +167 -0
- package/data/probe/npm/docs/CLAUDE_CODE_INTEGRATION.md +414 -0
- package/data/probe/npm/docs/CODEX_INTEGRATION.md +502 -0
- package/data/probe/npm/docs/EDIT_CREATE_TOOLS.md +233 -0
- package/data/probe/npm/docs/RETRY_AND_FALLBACK.md +674 -0
- package/data/probe/npm/example-usage.js +335 -0
- package/data/probe/npm/examples/multi-engine-demo.js +117 -0
- package/data/probe/npm/examples/probe-agent-cli.js +113 -0
- package/data/probe/npm/examples/test-agent-edit.js +114 -0
- package/data/probe/npm/examples/test-edit-create.js +120 -0
- package/data/probe/npm/examples/test-edit-direct.js +114 -0
- package/data/probe/npm/index.d.ts +744 -0
- package/data/probe/npm/jest.config.js +52 -0
- package/data/probe/npm/package.json +117 -0
- package/data/probe/npm/scripts/build-agent.cjs +75 -0
- package/data/probe/npm/scripts/build-cjs.js +124 -0
- package/data/probe/npm/scripts/build-mcp.cjs +36 -0
- package/data/probe/npm/scripts/postinstall.js +216 -0
- package/data/probe/npm/test-codex-e2e.js +78 -0
- package/data/probe/npm/test-download-lock.js +109 -0
- package/data/probe/npm/test-grep-security.js +94 -0
- package/data/probe/npm/test-grep-simplified.js +63 -0
- package/data/probe/npm/test-grep.js +51 -0
- package/data/probe/npm/tests/README.md +96 -0
- package/data/probe/npm/tests/agent-compact-history.test.js +174 -0
- package/data/probe/npm/tests/allow-tests-default.test.js +151 -0
- package/data/probe/npm/tests/contextCompactor.test.js +498 -0
- package/data/probe/npm/tests/delegate-config.test.js +353 -0
- package/data/probe/npm/tests/delegate-integration.test.js +348 -0
- package/data/probe/npm/tests/extractor-integration.test.js +162 -0
- package/data/probe/npm/tests/extractor.test.js +317 -0
- package/data/probe/npm/tests/fixtures/sampleDiagrams.js +267 -0
- package/data/probe/npm/tests/integration/claude-code-auto-fallback.spec.js +148 -0
- package/data/probe/npm/tests/integration/claude-code-multi-step.spec.js +127 -0
- package/data/probe/npm/tests/integration/claude-code-tool-events.spec.js +163 -0
- package/data/probe/npm/tests/integration/codex-auto-fallback.spec.js +191 -0
- package/data/probe/npm/tests/integration/codex-tool-events.spec.js +147 -0
- package/data/probe/npm/tests/integration/examplesChatMcp.test.js +402 -0
- package/data/probe/npm/tests/integration/mcpDotenvSupport.test.js +174 -0
- package/data/probe/npm/tests/integration/mcpErrorHandling.test.js +566 -0
- package/data/probe/npm/tests/integration/mcpRobustness.test.js +564 -0
- package/data/probe/npm/tests/integration/mcpStdoutPurity.test.js +355 -0
- package/data/probe/npm/tests/integration/probeAgentMcp.test.js +398 -0
- package/data/probe/npm/tests/integration/retryFallback.test.js +368 -0
- package/data/probe/npm/tests/integration/schema-in-initial-message.test.js +318 -0
- package/data/probe/npm/tests/integration/schema-validation-loop-prevention.test.js +244 -0
- package/data/probe/npm/tests/integration/schemaRetryLogic.test.js +94 -0
- package/data/probe/npm/tests/integration/validationFlow.test.js +329 -0
- package/data/probe/npm/tests/manual/test-codex-basic.js +110 -0
- package/data/probe/npm/tests/mcp/mcpClientManager.test.js +614 -0
- package/data/probe/npm/tests/mcp/mcpConfig.test.js +359 -0
- package/data/probe/npm/tests/mcp/mcpXmlBridge.test.js +436 -0
- package/data/probe/npm/tests/mcp/mockMcpServer.js +510 -0
- package/data/probe/npm/tests/mcp-strict-syntax.test.js +319 -0
- package/data/probe/npm/tests/mermaidQuoteEscaping.test.js +214 -0
- package/data/probe/npm/tests/nestedQuoteFix.test.js +40 -0
- package/data/probe/npm/tests/setup.js +46 -0
- package/data/probe/npm/tests/unit/allowed-tools.test.js +513 -0
- package/data/probe/npm/tests/unit/attempt-completion-closing-tag-in-content.test.js +188 -0
- package/data/probe/npm/tests/unit/attemptCompletionJsonFix.test.js +238 -0
- package/data/probe/npm/tests/unit/attemptCompletionJsonIssue.test.js +128 -0
- package/data/probe/npm/tests/unit/backtickAutoFix.test.js +35 -0
- package/data/probe/npm/tests/unit/bash-probe-agent-integration.test.js +389 -0
- package/data/probe/npm/tests/unit/bash-simple-commands.test.js +324 -0
- package/data/probe/npm/tests/unit/bash-tool-comprehensive.test.js +371 -0
- package/data/probe/npm/tests/unit/bash-tool-integration.test.js +310 -0
- package/data/probe/npm/tests/unit/bash-tool.test.js +341 -0
- package/data/probe/npm/tests/unit/completion-prompt.test.js +379 -0
- package/data/probe/npm/tests/unit/cwd-path-options.test.js +287 -0
- package/data/probe/npm/tests/unit/delegate-limits.test.js +422 -0
- package/data/probe/npm/tests/unit/direct-content-attempt-completion.test.js +235 -0
- package/data/probe/npm/tests/unit/edit-create-tools.test.js +609 -0
- package/data/probe/npm/tests/unit/enhancedMermaidValidation.test.js +577 -0
- package/data/probe/npm/tests/unit/extract-content.test.js +83 -0
- package/data/probe/npm/tests/unit/extract-multiple-targets.test.js +89 -0
- package/data/probe/npm/tests/unit/fallbackManager.test.js +442 -0
- package/data/probe/npm/tests/unit/githubCompatibilityValidation.test.js +258 -0
- package/data/probe/npm/tests/unit/imageConfig.test.js +149 -0
- package/data/probe/npm/tests/unit/imagePathResolution.test.js +345 -0
- package/data/probe/npm/tests/unit/json-fixing-agent.test.js +238 -0
- package/data/probe/npm/tests/unit/json-validation-enhanced-errors.test.js +199 -0
- package/data/probe/npm/tests/unit/jsonValidationInfiniteLoopFix.test.js +228 -0
- package/data/probe/npm/tests/unit/maidIntegration.test.js +139 -0
- package/data/probe/npm/tests/unit/maxIterationsWarning.test.js +195 -0
- package/data/probe/npm/tests/unit/mermaidEdgeLabelFix.test.js +161 -0
- package/data/probe/npm/tests/unit/mermaidHtmlEntities.test.js +76 -0
- package/data/probe/npm/tests/unit/mermaidInfiniteLoopFix.test.js +64 -0
- package/data/probe/npm/tests/unit/mermaidValidation.test.js +723 -0
- package/data/probe/npm/tests/unit/mermaidValidationVisorExample.test.js +309 -0
- package/data/probe/npm/tests/unit/probe-agent-clone-realistic.test.js +643 -0
- package/data/probe/npm/tests/unit/probe-agent-clone.test.js +476 -0
- package/data/probe/npm/tests/unit/probe-agent-delegate.test.js +400 -0
- package/data/probe/npm/tests/unit/probe-agent-model-option.test.js +118 -0
- package/data/probe/npm/tests/unit/probeTool-security.test.js +283 -0
- package/data/probe/npm/tests/unit/readImageTool.test.js +418 -0
- package/data/probe/npm/tests/unit/retryManager.test.js +317 -0
- package/data/probe/npm/tests/unit/schema-aware-reminders.test.js +288 -0
- package/data/probe/npm/tests/unit/schemaDefinitionDetection.test.js +115 -0
- package/data/probe/npm/tests/unit/schemaUtils.test.js +1268 -0
- package/data/probe/npm/tests/unit/simpleTelemetry.test.js +282 -0
- package/data/probe/npm/tests/unit/simplified-attempt-completion.test.js +274 -0
- package/data/probe/npm/tests/unit/single-quote-json-bug.test.js +231 -0
- package/data/probe/npm/tests/unit/subgraphAutoFix.test.js +110 -0
- package/data/probe/npm/tests/unit/system-prompt.test.js +32 -0
- package/data/probe/npm/tests/unit/types-probe-agent-options.test.js +42 -0
- package/data/probe/npm/tests/unit/xmlParsing.test.js +720 -0
- package/data/probe/npm/tsconfig.json +21 -0
- package/data/probe/result1.txt +19 -0
- package/data/probe/result2.txt +26 -0
- package/data/probe/scripts/benchmark.sh +270 -0
- package/data/probe/scripts/cache_memory_analysis.rs +844 -0
- package/data/probe/scripts/claude-hook-wrapper.sh +56 -0
- package/data/probe/site/.env.example +10 -0
- package/data/probe/site/DEPLOYMENT.md +86 -0
- package/data/probe/site/README.md +183 -0
- package/data/probe/site/adding-languages.md +135 -0
- package/data/probe/site/ai-chat.md +427 -0
- package/data/probe/site/ai-integration.md +1488 -0
- package/data/probe/site/blog/agentic-flow-custom-xml-protocol.md +407 -0
- package/data/probe/site/blog/index.md +118 -0
- package/data/probe/site/blog/v0.6.0-release.md +426 -0
- package/data/probe/site/blog.md +8 -0
- package/data/probe/site/changelog.md +200 -0
- package/data/probe/site/cli-mode.md +437 -0
- package/data/probe/site/code-extraction.md +436 -0
- package/data/probe/site/contributing/README.md +9 -0
- package/data/probe/site/contributing/documentation-cross-references.md +215 -0
- package/data/probe/site/contributing/documentation-maintenance.md +275 -0
- package/data/probe/site/contributing/documentation-structure.md +75 -0
- package/data/probe/site/documentation-cross-references.md +215 -0
- package/data/probe/site/documentation-guide.md +132 -0
- package/data/probe/site/documentation-maintenance.md +275 -0
- package/data/probe/site/features.md +147 -0
- package/data/probe/site/how-it-works.md +118 -0
- package/data/probe/site/index.md +175 -0
- package/data/probe/site/index.md.bak +133 -0
- package/data/probe/site/installation.md +235 -0
- package/data/probe/site/integrations/docker.md +248 -0
- package/data/probe/site/integrations/github-actions.md +413 -0
- package/data/probe/site/language-support-overview.md +168 -0
- package/data/probe/site/mcp-integration.md +587 -0
- package/data/probe/site/mcp-server.md +304 -0
- package/data/probe/site/navigation-structure.md +76 -0
- package/data/probe/site/nodejs-sdk.md +798 -0
- package/data/probe/site/output-formats.md +625 -0
- package/data/probe/site/package.json +21 -0
- package/data/probe/site/public/_headers +28 -0
- package/data/probe/site/public/_redirects +11 -0
- package/data/probe/site/quick-start.md +289 -0
- package/data/probe/site/search-functionality.md +291 -0
- package/data/probe/site/search-reference.md +291 -0
- package/data/probe/site/supported-languages.md +215 -0
- package/data/probe/site/use-cases/README.md +8 -0
- package/data/probe/site/use-cases/advanced-cli.md +253 -0
- package/data/probe/site/use-cases/ai-code-editors.md +239 -0
- package/data/probe/site/use-cases/building-ai-tools.md +529 -0
- package/data/probe/site/use-cases/cli-ai-workflows.md +285 -0
- package/data/probe/site/use-cases/deploying-probe-web-interface.md +255 -0
- package/data/probe/site/use-cases/integrating-probe-into-ai-code-editors.md +161 -0
- package/data/probe/site/use-cases/nodejs-sdk.md +596 -0
- package/data/probe/site/use-cases/team-chat.md +350 -0
- package/data/probe/site/web-interface.md +434 -0
- package/data/probe/site/wrangler.toml +9 -0
- package/data/probe/test-api-key.sh +1 -0
- package/data/probe/test-probe-implementation/hello.js +7 -0
- package/data/probe/test_cases/demonstrate_early_termination_issues.sh +176 -0
- package/data/probe/test_cases/early_termination_issues.rs +533 -0
- package/data/probe/test_data/test_nested_struct.go +26 -0
- package/data/probe/tests/README.md +286 -0
- package/data/probe/tests/README_search_determinism_tests.md +116 -0
- package/data/probe/tests/adjacent_comment_test.rs +152 -0
- package/data/probe/tests/apostrophe_handling_tests.rs +132 -0
- package/data/probe/tests/block_filtering_with_ast_tests.rs +669 -0
- package/data/probe/tests/block_merging_tests.rs +396 -0
- package/data/probe/tests/c_outline_format_tests.rs +2179 -0
- package/data/probe/tests/cache_invalidation_issues.rs.disabled +682 -0
- package/data/probe/tests/cache_order_tests.rs +147 -0
- package/data/probe/tests/cache_query_scoping_tests.rs +221 -0
- package/data/probe/tests/cli_tests.rs +680 -0
- package/data/probe/tests/comment_context_integration_test.rs +240 -0
- package/data/probe/tests/common.rs +33 -0
- package/data/probe/tests/complex_block_merging_tests.rs +599 -0
- package/data/probe/tests/complex_query_block_filtering_tests.rs +422 -0
- package/data/probe/tests/control_flow_closing_braces_test.rs +91 -0
- package/data/probe/tests/cpp_outline_format_tests.rs +1507 -0
- package/data/probe/tests/csharp_outline_format_tests.rs +941 -0
- package/data/probe/tests/elastic_query_integration_tests.rs +922 -0
- package/data/probe/tests/extract_command_tests.rs +1848 -0
- package/data/probe/tests/extract_deduplication_tests.rs +146 -0
- package/data/probe/tests/extract_input_file_tests.rs +84 -0
- package/data/probe/tests/extract_prompt_tests.rs +102 -0
- package/data/probe/tests/filename_search_tests.rs +96 -0
- package/data/probe/tests/fixtures/user/AssemblyInfo.cs +3 -0
- package/data/probe/tests/github_extract_tests.rs +234 -0
- package/data/probe/tests/go_comment_test.rs +253 -0
- package/data/probe/tests/go_outline_format_tests.rs +2587 -0
- package/data/probe/tests/go_path_resolver_tests.rs +96 -0
- package/data/probe/tests/html_outline_format_tests.rs +637 -0
- package/data/probe/tests/integration_tests.rs +837 -0
- package/data/probe/tests/ip_whitelist_test.rs +148 -0
- package/data/probe/tests/java_outline_format_tests.rs +1611 -0
- package/data/probe/tests/javascript_extract_tests.rs +315 -0
- package/data/probe/tests/javascript_outline_format_tests.rs +1464 -0
- package/data/probe/tests/json_format_tests.rs +436 -0
- package/data/probe/tests/json_schema_validation_tests.rs +450 -0
- package/data/probe/tests/lib_usage.rs +60 -0
- package/data/probe/tests/line_comment_context_extension_test.rs +459 -0
- package/data/probe/tests/line_map_cache_tests.rs +114 -0
- package/data/probe/tests/markdown_integration_tests.rs +190 -0
- package/data/probe/tests/mocks/test_ip_whitelist.go +11 -0
- package/data/probe/tests/mocks/test_object.js +27 -0
- package/data/probe/tests/mocks/test_struct.go +50 -0
- package/data/probe/tests/multi_keyword_pattern_tests.rs +464 -0
- package/data/probe/tests/multi_language_syntax_integration_tests.rs +218 -0
- package/data/probe/tests/multiple_capture_groups_tests.rs +169 -0
- package/data/probe/tests/negative_compound_word_tests.rs +246 -0
- package/data/probe/tests/nested_symbol_extraction_tests.rs +99 -0
- package/data/probe/tests/outline_cross_file_interference_test.rs +335 -0
- package/data/probe/tests/outline_keyword_preservation_test.rs +67 -0
- package/data/probe/tests/output_format_edge_cases_tests.rs +693 -0
- package/data/probe/tests/parallel_extraction_tests.rs +178 -0
- package/data/probe/tests/parallel_search_tests.rs +355 -0
- package/data/probe/tests/path_resolver_tests.rs +698 -0
- package/data/probe/tests/php_outline_format_extended_tests.rs +928 -0
- package/data/probe/tests/php_outline_format_tests.rs +768 -0
- package/data/probe/tests/property_tests.proptest-regressions +9 -0
- package/data/probe/tests/property_tests.rs +118 -0
- package/data/probe/tests/python_outline_format_tests.rs +1538 -0
- package/data/probe/tests/query_command_json_tests.rs +438 -0
- package/data/probe/tests/query_command_tests.rs +232 -0
- package/data/probe/tests/query_command_xml_tests.rs +569 -0
- package/data/probe/tests/quoted_term_with_negative_keyword_tests.rs +216 -0
- package/data/probe/tests/required_terms_filename_tests.rs +116 -0
- package/data/probe/tests/ruby_outline_format_tests.rs +1011 -0
- package/data/probe/tests/rust_line_comment_context_test.rs +151 -0
- package/data/probe/tests/rust_outline_format_enhanced_tests.rs +725 -0
- package/data/probe/tests/rust_outline_format_tests.rs +843 -0
- package/data/probe/tests/schemas/xml_output_schema.xsd +38 -0
- package/data/probe/tests/search_determinism_tests.rs +451 -0
- package/data/probe/tests/search_hints_tests.rs +253 -0
- package/data/probe/tests/special_character_escaping_tests.rs +417 -0
- package/data/probe/tests/stemming_compound_word_filtering_tests.rs +535 -0
- package/data/probe/tests/strict_elastic_syntax_tests.rs +404 -0
- package/data/probe/tests/swift_outline_format_tests.rs +3319 -0
- package/data/probe/tests/symbols_tests.rs +166 -0
- package/data/probe/tests/test_file.rs +45 -0
- package/data/probe/tests/test_tokenize.rs +28 -0
- package/data/probe/tests/timeout_tests.rs +82 -0
- package/data/probe/tests/tokenization_tests.rs +195 -0
- package/data/probe/tests/tokenized_block_filtering_tests.rs +174 -0
- package/data/probe/tests/typescript_extract_tests.rs +214 -0
- package/data/probe/tests/typescript_outline_format_tests.rs +2188 -0
- package/data/probe/tests/xml_format_tests.rs +568 -0
- package/data/probe/tests/xml_schema_validation_tests.rs +497 -0
- package/data/scripts/postinstall.mjs +9 -0
- package/data/scripts/set-version.js +0 -0
- package/data/scripts/wiki-build.sh +111 -0
- package/data/scripts/wiki-deploy.sh +73 -0
- package/data/serve.json +12 -0
- package/data/test/demo-dynamic.html +134 -0
- package/data/test/demo-esm.html +105 -0
- package/data/test/demo-iife.html +78 -0
- package/data/tsconfig.json +7 -0
- package/data/vite.server.ts +483 -0
- package/data/vitest.config.ts +40 -0
- package/data/wiki/Home.md +58 -0
- package/data/wiki/_Sidebar.md +39 -0
- package/docs-mcp.config.json +20 -0
- package/package.json +56 -0
- package/src/config.js +111 -0
- package/src/index.js +395 -0
|
@@ -0,0 +1,2188 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use std::fs;
|
|
3
|
+
use tempfile::TempDir;
|
|
4
|
+
|
|
5
|
+
mod common;
|
|
6
|
+
use common::TestContext;
|
|
7
|
+
|
|
8
|
+
#[test]
|
|
9
|
+
fn test_typescript_outline_basic_symbols() -> Result<()> {
|
|
10
|
+
let temp_dir = TempDir::new()?;
|
|
11
|
+
let test_file = temp_dir.path().join("basic.ts");
|
|
12
|
+
|
|
13
|
+
let content = r#"// TypeScript classes, interfaces, and types for testing
|
|
14
|
+
|
|
15
|
+
import { Observable } from 'rxjs';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Interface for calculator operations.
|
|
19
|
+
*/
|
|
20
|
+
interface CalculatorOperations {
|
|
21
|
+
add(x: number, y: number): number;
|
|
22
|
+
subtract(x: number, y: number): number;
|
|
23
|
+
multiply(x: number, y: number): number;
|
|
24
|
+
divide(x: number, y: number): number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Type alias for calculation result.
|
|
29
|
+
*/
|
|
30
|
+
type CalculationResult = {
|
|
31
|
+
value: number;
|
|
32
|
+
operation: string;
|
|
33
|
+
timestamp: Date;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generic type for API responses.
|
|
38
|
+
*/
|
|
39
|
+
type ApiResponse<T> = {
|
|
40
|
+
data: T;
|
|
41
|
+
success: boolean;
|
|
42
|
+
message?: string;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Enum for calculation operations.
|
|
47
|
+
*/
|
|
48
|
+
enum OperationType {
|
|
49
|
+
ADDITION = 'ADD',
|
|
50
|
+
SUBTRACTION = 'SUB',
|
|
51
|
+
MULTIPLICATION = 'MUL',
|
|
52
|
+
DIVISION = 'DIV',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Abstract base class for calculators.
|
|
57
|
+
*/
|
|
58
|
+
abstract class BaseCalculator {
|
|
59
|
+
protected history: CalculationResult[] = [];
|
|
60
|
+
|
|
61
|
+
abstract calculate(operation: OperationType, x: number, y: number): number;
|
|
62
|
+
|
|
63
|
+
getHistory(): readonly CalculationResult[] {
|
|
64
|
+
return this.history;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected recordOperation(operation: OperationType, result: number): void {
|
|
68
|
+
this.history.push({
|
|
69
|
+
value: result,
|
|
70
|
+
operation: operation.toString(),
|
|
71
|
+
timestamp: new Date(),
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Advanced calculator implementation.
|
|
78
|
+
*/
|
|
79
|
+
class AdvancedCalculator extends BaseCalculator implements CalculatorOperations {
|
|
80
|
+
private precision: number;
|
|
81
|
+
|
|
82
|
+
constructor(precision: number = 2) {
|
|
83
|
+
super();
|
|
84
|
+
this.precision = precision;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
add(x: number, y: number): number {
|
|
88
|
+
const result = this.roundToPrecision(x + y);
|
|
89
|
+
this.recordOperation(OperationType.ADDITION, result);
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
subtract(x: number, y: number): number {
|
|
94
|
+
const result = this.roundToPrecision(x - y);
|
|
95
|
+
this.recordOperation(OperationType.SUBTRACTION, result);
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
multiply(x: number, y: number): number {
|
|
100
|
+
const result = this.roundToPrecision(x * y);
|
|
101
|
+
this.recordOperation(OperationType.MULTIPLICATION, result);
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
divide(x: number, y: number): number {
|
|
106
|
+
if (y === 0) {
|
|
107
|
+
throw new Error('Division by zero is not allowed');
|
|
108
|
+
}
|
|
109
|
+
const result = this.roundToPrecision(x / y);
|
|
110
|
+
this.recordOperation(OperationType.DIVISION, result);
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
calculate(operation: OperationType, x: number, y: number): number {
|
|
115
|
+
switch (operation) {
|
|
116
|
+
case OperationType.ADDITION:
|
|
117
|
+
return this.add(x, y);
|
|
118
|
+
case OperationType.SUBTRACTION:
|
|
119
|
+
return this.subtract(x, y);
|
|
120
|
+
case OperationType.MULTIPLICATION:
|
|
121
|
+
return this.multiply(x, y);
|
|
122
|
+
case OperationType.DIVISION:
|
|
123
|
+
return this.divide(x, y);
|
|
124
|
+
default:
|
|
125
|
+
throw new Error(`Unsupported operation: ${operation}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private roundToPrecision(value: number): number {
|
|
130
|
+
return Math.round(value * Math.pow(10, this.precision)) / Math.pow(10, this.precision);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Factory function for creating calculators.
|
|
136
|
+
*/
|
|
137
|
+
function createCalculator<T extends BaseCalculator>(
|
|
138
|
+
CalculatorClass: new (...args: any[]) => T,
|
|
139
|
+
...args: any[]
|
|
140
|
+
): T {
|
|
141
|
+
return new CalculatorClass(...args);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Async function for fetching calculator settings.
|
|
146
|
+
*/
|
|
147
|
+
async function fetchCalculatorSettings(): Promise<ApiResponse<{ precision: number }>> {
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
resolve({
|
|
151
|
+
data: { precision: 4 },
|
|
152
|
+
success: true,
|
|
153
|
+
message: 'Settings loaded successfully',
|
|
154
|
+
});
|
|
155
|
+
}, 100);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Generic utility function with constraints.
|
|
161
|
+
*/
|
|
162
|
+
function processResults<T extends { value: number }>(results: T[]): T[] {
|
|
163
|
+
return results.filter(result => result.value !== 0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Export all types and classes
|
|
167
|
+
export {
|
|
168
|
+
CalculatorOperations,
|
|
169
|
+
CalculationResult,
|
|
170
|
+
ApiResponse,
|
|
171
|
+
OperationType,
|
|
172
|
+
BaseCalculator,
|
|
173
|
+
AdvancedCalculator,
|
|
174
|
+
createCalculator,
|
|
175
|
+
fetchCalculatorSettings,
|
|
176
|
+
processResults,
|
|
177
|
+
};
|
|
178
|
+
"#;
|
|
179
|
+
|
|
180
|
+
fs::write(&test_file, content)?;
|
|
181
|
+
|
|
182
|
+
let ctx = TestContext::new();
|
|
183
|
+
let output = ctx.run_probe(&[
|
|
184
|
+
"search",
|
|
185
|
+
"Calculator", // Search for Calculator-related symbols
|
|
186
|
+
test_file.to_str().unwrap(),
|
|
187
|
+
"--format",
|
|
188
|
+
"outline",
|
|
189
|
+
"--allow-tests",
|
|
190
|
+
"--exact",
|
|
191
|
+
])?;
|
|
192
|
+
|
|
193
|
+
let output2 = ctx.run_probe(&[
|
|
194
|
+
"search",
|
|
195
|
+
"interface", // Search for interface declarations
|
|
196
|
+
test_file.to_str().unwrap(),
|
|
197
|
+
"--format",
|
|
198
|
+
"outline",
|
|
199
|
+
"--allow-tests",
|
|
200
|
+
"--exact",
|
|
201
|
+
])?;
|
|
202
|
+
|
|
203
|
+
// Verify TypeScript symbols are extracted in outline format
|
|
204
|
+
assert!(
|
|
205
|
+
output.contains("Calculator") || output2.contains("CalculatorOperations"),
|
|
206
|
+
"Missing Calculator-related symbols - output: {} | output2: {}",
|
|
207
|
+
output,
|
|
208
|
+
output2
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Test interface search
|
|
212
|
+
assert!(
|
|
213
|
+
output2.contains("interface"),
|
|
214
|
+
"Missing interface declarations - output2: {}",
|
|
215
|
+
output2
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
Ok(())
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
#[test]
|
|
222
|
+
fn test_typescript_outline_generic_types() -> Result<()> {
|
|
223
|
+
let temp_dir = TempDir::new()?;
|
|
224
|
+
let test_file = temp_dir.path().join("generics.ts");
|
|
225
|
+
|
|
226
|
+
let content = r#"// Advanced TypeScript generics and conditional types
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Generic repository interface with CRUD operations.
|
|
230
|
+
*/
|
|
231
|
+
interface Repository<T, K = string> {
|
|
232
|
+
create(entity: Omit<T, 'id'>): Promise<T>;
|
|
233
|
+
findById(id: K): Promise<T | null>;
|
|
234
|
+
findAll(filter?: Partial<T>): Promise<T[]>;
|
|
235
|
+
update(id: K, updates: Partial<T>): Promise<T>;
|
|
236
|
+
delete(id: K): Promise<boolean>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Conditional types for API responses.
|
|
241
|
+
*/
|
|
242
|
+
type ApiResult<T, E = Error> = T extends string
|
|
243
|
+
? { message: T; success: true }
|
|
244
|
+
: T extends Error
|
|
245
|
+
? { error: T; success: false }
|
|
246
|
+
: { data: T; success: boolean; error?: E };
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Mapped types for form validation.
|
|
250
|
+
*/
|
|
251
|
+
type ValidationRules<T> = {
|
|
252
|
+
[K in keyof T]: {
|
|
253
|
+
required?: boolean;
|
|
254
|
+
minLength?: number;
|
|
255
|
+
maxLength?: number;
|
|
256
|
+
pattern?: RegExp;
|
|
257
|
+
custom?: (value: T[K]) => string | null;
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Utility types for object manipulation.
|
|
263
|
+
*/
|
|
264
|
+
type DeepPartial<T> = {
|
|
265
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
type DeepRequired<T> = {
|
|
269
|
+
[P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Generic service class with dependency injection.
|
|
274
|
+
*/
|
|
275
|
+
abstract class BaseService<TEntity, TRepository extends Repository<TEntity>> {
|
|
276
|
+
protected repository: TRepository;
|
|
277
|
+
|
|
278
|
+
constructor(repository: TRepository) {
|
|
279
|
+
this.repository = repository;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async create(entityData: Omit<TEntity, 'id'>): Promise<ApiResult<TEntity>> {
|
|
283
|
+
try {
|
|
284
|
+
const entity = await this.repository.create(entityData);
|
|
285
|
+
return { data: entity, success: true };
|
|
286
|
+
} catch (error) {
|
|
287
|
+
return { data: {} as TEntity, success: false, error: error as Error };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
abstract validate(entity: Partial<TEntity>): ValidationResult<TEntity>;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Validation result type.
|
|
296
|
+
*/
|
|
297
|
+
interface ValidationResult<T> {
|
|
298
|
+
isValid: boolean;
|
|
299
|
+
errors: Partial<Record<keyof T, string>>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Generic validator class.
|
|
304
|
+
*/
|
|
305
|
+
class Validator<T extends Record<string, any>> {
|
|
306
|
+
private rules: ValidationRules<T>;
|
|
307
|
+
|
|
308
|
+
constructor(rules: ValidationRules<T>) {
|
|
309
|
+
this.rules = rules;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
validate(data: Partial<T>): ValidationResult<T> {
|
|
313
|
+
const errors: Partial<Record<keyof T, string>> = {};
|
|
314
|
+
|
|
315
|
+
for (const [key, rule] of Object.entries(this.rules) as [keyof T, any][]) {
|
|
316
|
+
const value = data[key];
|
|
317
|
+
|
|
318
|
+
if (rule.required && (value === undefined || value === null || value === '')) {
|
|
319
|
+
errors[key] = `${String(key)} is required`;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (value && typeof value === 'string') {
|
|
324
|
+
if (rule.minLength && value.length < rule.minLength) {
|
|
325
|
+
errors[key] = `${String(key)} must be at least ${rule.minLength} characters`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (rule.maxLength && value.length > rule.maxLength) {
|
|
329
|
+
errors[key] = `${String(key)} must not exceed ${rule.maxLength} characters`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (rule.pattern && !rule.pattern.test(value)) {
|
|
333
|
+
errors[key] = `${String(key)} format is invalid`;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (rule.custom && value !== undefined) {
|
|
338
|
+
const customError = rule.custom(value);
|
|
339
|
+
if (customError) {
|
|
340
|
+
errors[key] = customError;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
isValid: Object.keys(errors).length === 0,
|
|
347
|
+
errors,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* User entity type for demonstration.
|
|
354
|
+
*/
|
|
355
|
+
interface User {
|
|
356
|
+
id: string;
|
|
357
|
+
username: string;
|
|
358
|
+
email: string;
|
|
359
|
+
firstName: string;
|
|
360
|
+
lastName: string;
|
|
361
|
+
age: number;
|
|
362
|
+
isActive: boolean;
|
|
363
|
+
createdAt: Date;
|
|
364
|
+
updatedAt: Date;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* User service implementation.
|
|
369
|
+
*/
|
|
370
|
+
class UserService extends BaseService<User, Repository<User>> {
|
|
371
|
+
private validator: Validator<User>;
|
|
372
|
+
|
|
373
|
+
constructor(repository: Repository<User>) {
|
|
374
|
+
super(repository);
|
|
375
|
+
this.validator = new Validator<User>({
|
|
376
|
+
username: {
|
|
377
|
+
required: true,
|
|
378
|
+
minLength: 3,
|
|
379
|
+
maxLength: 20,
|
|
380
|
+
pattern: /^[a-zA-Z0-9_]+$/,
|
|
381
|
+
},
|
|
382
|
+
email: {
|
|
383
|
+
required: true,
|
|
384
|
+
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
385
|
+
},
|
|
386
|
+
firstName: { required: true, minLength: 1 },
|
|
387
|
+
lastName: { required: true, minLength: 1 },
|
|
388
|
+
age: {
|
|
389
|
+
custom: (age: number) => {
|
|
390
|
+
if (age < 0 || age > 150) {
|
|
391
|
+
return 'Age must be between 0 and 150';
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
validate(entity: Partial<User>): ValidationResult<User> {
|
|
400
|
+
return this.validator.validate(entity);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async createUser(userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<ApiResult<User>> {
|
|
404
|
+
const validation = this.validate(userData);
|
|
405
|
+
|
|
406
|
+
if (!validation.isValid) {
|
|
407
|
+
return {
|
|
408
|
+
data: {} as User,
|
|
409
|
+
success: false,
|
|
410
|
+
error: new Error(`Validation failed: ${JSON.stringify(validation.errors)}`),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const userWithTimestamps = {
|
|
415
|
+
...userData,
|
|
416
|
+
createdAt: new Date(),
|
|
417
|
+
updatedAt: new Date(),
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
return this.create(userWithTimestamps);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Generic factory function with complex constraints.
|
|
426
|
+
*/
|
|
427
|
+
function createService<
|
|
428
|
+
TEntity extends { id: string },
|
|
429
|
+
TRepo extends Repository<TEntity>
|
|
430
|
+
>(
|
|
431
|
+
ServiceClass: new (repo: TRepo) => BaseService<TEntity, TRepo>,
|
|
432
|
+
repository: TRepo
|
|
433
|
+
): BaseService<TEntity, TRepo> {
|
|
434
|
+
return new ServiceClass(repository);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export {
|
|
438
|
+
Repository,
|
|
439
|
+
ApiResult,
|
|
440
|
+
ValidationRules,
|
|
441
|
+
DeepPartial,
|
|
442
|
+
DeepRequired,
|
|
443
|
+
BaseService,
|
|
444
|
+
ValidationResult,
|
|
445
|
+
Validator,
|
|
446
|
+
User,
|
|
447
|
+
UserService,
|
|
448
|
+
createService,
|
|
449
|
+
};
|
|
450
|
+
"#;
|
|
451
|
+
|
|
452
|
+
fs::write(&test_file, content)?;
|
|
453
|
+
|
|
454
|
+
let ctx = TestContext::new();
|
|
455
|
+
let output = ctx.run_probe(&[
|
|
456
|
+
"search",
|
|
457
|
+
"Repository", // Search for Repository-related symbols
|
|
458
|
+
test_file.to_str().unwrap(),
|
|
459
|
+
"--format",
|
|
460
|
+
"outline",
|
|
461
|
+
"--allow-tests",
|
|
462
|
+
"--exact",
|
|
463
|
+
])?;
|
|
464
|
+
|
|
465
|
+
let output2 = ctx.run_probe(&[
|
|
466
|
+
"search",
|
|
467
|
+
"type", // Search for type declarations
|
|
468
|
+
test_file.to_str().unwrap(),
|
|
469
|
+
"--format",
|
|
470
|
+
"outline",
|
|
471
|
+
"--allow-tests",
|
|
472
|
+
"--exact",
|
|
473
|
+
])?;
|
|
474
|
+
|
|
475
|
+
// Verify complex TypeScript features
|
|
476
|
+
assert!(
|
|
477
|
+
output.contains("Repository") || output2.contains("Repository"),
|
|
478
|
+
"Missing Repository-related symbols - output: {} | output2: {}",
|
|
479
|
+
output,
|
|
480
|
+
output2
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
// Test type search
|
|
484
|
+
assert!(
|
|
485
|
+
output2.contains("type"),
|
|
486
|
+
"Missing type declarations - output2: {}",
|
|
487
|
+
output2
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
Ok(())
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
#[test]
|
|
494
|
+
fn test_typescript_outline_react_components() -> Result<()> {
|
|
495
|
+
let temp_dir = TempDir::new()?;
|
|
496
|
+
let test_file = temp_dir.path().join("react_components.tsx");
|
|
497
|
+
|
|
498
|
+
let content = r#"import React, { useState, useEffect, useCallback, useMemo, ReactNode } from 'react';
|
|
499
|
+
|
|
500
|
+
// TypeScript interfaces for props
|
|
501
|
+
interface BaseProps {
|
|
502
|
+
className?: string;
|
|
503
|
+
children?: ReactNode;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
interface UserData {
|
|
507
|
+
id: string;
|
|
508
|
+
name: string;
|
|
509
|
+
email: string;
|
|
510
|
+
avatar?: string;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
interface UserCardProps extends BaseProps {
|
|
514
|
+
user: UserData;
|
|
515
|
+
onEdit?: (user: UserData) => void;
|
|
516
|
+
onDelete?: (userId: string) => void;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
interface UserListProps extends BaseProps {
|
|
520
|
+
users: UserData[];
|
|
521
|
+
loading?: boolean;
|
|
522
|
+
onUserAction?: (action: 'edit' | 'delete', user: UserData) => void;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Custom hook with TypeScript
|
|
526
|
+
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
|
|
527
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
528
|
+
try {
|
|
529
|
+
const item = window.localStorage.getItem(key);
|
|
530
|
+
return item ? JSON.parse(item) : initialValue;
|
|
531
|
+
} catch (error) {
|
|
532
|
+
console.error(`Error reading localStorage key "${key}":`, error);
|
|
533
|
+
return initialValue;
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const setValue = useCallback((value: T) => {
|
|
538
|
+
try {
|
|
539
|
+
setStoredValue(value);
|
|
540
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
541
|
+
} catch (error) {
|
|
542
|
+
console.error(`Error setting localStorage key "${key}":`, error);
|
|
543
|
+
}
|
|
544
|
+
}, [key]);
|
|
545
|
+
|
|
546
|
+
return [storedValue, setValue];
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Generic component with constraints
|
|
550
|
+
interface ListProps<T> extends BaseProps {
|
|
551
|
+
items: T[];
|
|
552
|
+
renderItem: (item: T, index: number) => ReactNode;
|
|
553
|
+
keyExtractor: (item: T) => string;
|
|
554
|
+
emptyMessage?: string;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function List<T>({
|
|
558
|
+
items,
|
|
559
|
+
renderItem,
|
|
560
|
+
keyExtractor,
|
|
561
|
+
emptyMessage = "No items to display",
|
|
562
|
+
className,
|
|
563
|
+
children
|
|
564
|
+
}: ListProps<T>): JSX.Element {
|
|
565
|
+
if (items.length === 0) {
|
|
566
|
+
return (
|
|
567
|
+
<div className={`empty-list ${className || ''}`}>
|
|
568
|
+
{emptyMessage}
|
|
569
|
+
{children}
|
|
570
|
+
</div>
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return (
|
|
575
|
+
<div className={`list ${className || ''}`}>
|
|
576
|
+
{items.map((item, index) => (
|
|
577
|
+
<div key={keyExtractor(item)} className="list-item">
|
|
578
|
+
{renderItem(item, index)}
|
|
579
|
+
</div>
|
|
580
|
+
))}
|
|
581
|
+
{children}
|
|
582
|
+
</div>
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Functional component with complex state management
|
|
587
|
+
const UserCard: React.FC<UserCardProps> = ({
|
|
588
|
+
user,
|
|
589
|
+
onEdit,
|
|
590
|
+
onDelete,
|
|
591
|
+
className,
|
|
592
|
+
children
|
|
593
|
+
}) => {
|
|
594
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
595
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
596
|
+
|
|
597
|
+
const handleEdit = useCallback(() => {
|
|
598
|
+
onEdit?.(user);
|
|
599
|
+
}, [user, onEdit]);
|
|
600
|
+
|
|
601
|
+
const handleDelete = useCallback(() => {
|
|
602
|
+
if (window.confirm(`Are you sure you want to delete ${user.name}?`)) {
|
|
603
|
+
onDelete?.(user.id);
|
|
604
|
+
}
|
|
605
|
+
}, [user, onDelete]);
|
|
606
|
+
|
|
607
|
+
const displayName = useMemo(() => {
|
|
608
|
+
return user.name.length > 20
|
|
609
|
+
? `${user.name.substring(0, 20)}...`
|
|
610
|
+
: user.name;
|
|
611
|
+
}, [user.name]);
|
|
612
|
+
|
|
613
|
+
return (
|
|
614
|
+
<div
|
|
615
|
+
className={`user-card ${className || ''} ${isHovered ? 'hovered' : ''}`}
|
|
616
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
617
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
618
|
+
>
|
|
619
|
+
<div className="user-header">
|
|
620
|
+
{user.avatar && (
|
|
621
|
+
<img
|
|
622
|
+
src={user.avatar}
|
|
623
|
+
alt={`${user.name}'s avatar`}
|
|
624
|
+
className="user-avatar"
|
|
625
|
+
/>
|
|
626
|
+
)}
|
|
627
|
+
<div className="user-info">
|
|
628
|
+
<h3 className="user-name" title={user.name}>
|
|
629
|
+
{displayName}
|
|
630
|
+
</h3>
|
|
631
|
+
<p className="user-email">{user.email}</p>
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
|
|
635
|
+
{isExpanded && (
|
|
636
|
+
<div className="user-details">
|
|
637
|
+
<p>ID: {user.id}</p>
|
|
638
|
+
{children}
|
|
639
|
+
</div>
|
|
640
|
+
)}
|
|
641
|
+
|
|
642
|
+
<div className="user-actions">
|
|
643
|
+
<button
|
|
644
|
+
onClick={() => setIsExpanded(!isExpanded)}
|
|
645
|
+
className="expand-button"
|
|
646
|
+
>
|
|
647
|
+
{isExpanded ? 'Collapse' : 'Expand'}
|
|
648
|
+
</button>
|
|
649
|
+
|
|
650
|
+
{onEdit && (
|
|
651
|
+
<button onClick={handleEdit} className="edit-button">
|
|
652
|
+
Edit
|
|
653
|
+
</button>
|
|
654
|
+
)}
|
|
655
|
+
|
|
656
|
+
{onDelete && (
|
|
657
|
+
<button onClick={handleDelete} className="delete-button">
|
|
658
|
+
Delete
|
|
659
|
+
</button>
|
|
660
|
+
)}
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
);
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
// Component with complex effects and error handling
|
|
667
|
+
const UserList: React.FC<UserListProps> = ({
|
|
668
|
+
users,
|
|
669
|
+
loading = false,
|
|
670
|
+
onUserAction,
|
|
671
|
+
className,
|
|
672
|
+
children
|
|
673
|
+
}) => {
|
|
674
|
+
const [filteredUsers, setFilteredUsers] = useState<UserData[]>([]);
|
|
675
|
+
const [searchTerm, setSearchTerm] = useLocalStorage<string>('userSearch', '');
|
|
676
|
+
const [error, setError] = useState<string | null>(null);
|
|
677
|
+
|
|
678
|
+
// Effect for filtering users
|
|
679
|
+
useEffect(() => {
|
|
680
|
+
try {
|
|
681
|
+
if (!searchTerm.trim()) {
|
|
682
|
+
setFilteredUsers(users);
|
|
683
|
+
} else {
|
|
684
|
+
const filtered = users.filter(user =>
|
|
685
|
+
user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
686
|
+
user.email.toLowerCase().includes(searchTerm.toLowerCase())
|
|
687
|
+
);
|
|
688
|
+
setFilteredUsers(filtered);
|
|
689
|
+
}
|
|
690
|
+
setError(null);
|
|
691
|
+
} catch (err) {
|
|
692
|
+
setError('Failed to filter users');
|
|
693
|
+
console.error('Filter error:', err);
|
|
694
|
+
}
|
|
695
|
+
}, [users, searchTerm]);
|
|
696
|
+
|
|
697
|
+
const handleUserAction = useCallback((action: 'edit' | 'delete', user: UserData) => {
|
|
698
|
+
try {
|
|
699
|
+
onUserAction?.(action, user);
|
|
700
|
+
} catch (err) {
|
|
701
|
+
setError(`Failed to ${action} user`);
|
|
702
|
+
console.error(`${action} error:`, err);
|
|
703
|
+
}
|
|
704
|
+
}, [onUserAction]);
|
|
705
|
+
|
|
706
|
+
if (loading) {
|
|
707
|
+
return (
|
|
708
|
+
<div className={`user-list loading ${className || ''}`}>
|
|
709
|
+
<div className="loading-spinner">Loading users...</div>
|
|
710
|
+
{children}
|
|
711
|
+
</div>
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (error) {
|
|
716
|
+
return (
|
|
717
|
+
<div className={`user-list error ${className || ''}`}>
|
|
718
|
+
<div className="error-message">Error: {error}</div>
|
|
719
|
+
<button onClick={() => setError(null)}>Dismiss</button>
|
|
720
|
+
{children}
|
|
721
|
+
</div>
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return (
|
|
726
|
+
<div className={`user-list ${className || ''}`}>
|
|
727
|
+
<div className="search-container">
|
|
728
|
+
<input
|
|
729
|
+
type="text"
|
|
730
|
+
value={searchTerm}
|
|
731
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
732
|
+
placeholder="Search users..."
|
|
733
|
+
className="search-input"
|
|
734
|
+
/>
|
|
735
|
+
</div>
|
|
736
|
+
|
|
737
|
+
<List
|
|
738
|
+
items={filteredUsers}
|
|
739
|
+
keyExtractor={(user) => user.id}
|
|
740
|
+
renderItem={(user) => (
|
|
741
|
+
<UserCard
|
|
742
|
+
user={user}
|
|
743
|
+
onEdit={(user) => handleUserAction('edit', user)}
|
|
744
|
+
onDelete={(userId) => {
|
|
745
|
+
const user = filteredUsers.find(u => u.id === userId);
|
|
746
|
+
if (user) handleUserAction('delete', user);
|
|
747
|
+
}}
|
|
748
|
+
/>
|
|
749
|
+
)}
|
|
750
|
+
emptyMessage="No users found"
|
|
751
|
+
/>
|
|
752
|
+
|
|
753
|
+
{children}
|
|
754
|
+
</div>
|
|
755
|
+
);
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
// Higher-order component with TypeScript
|
|
759
|
+
function withErrorBoundary<P extends object>(
|
|
760
|
+
WrappedComponent: React.ComponentType<P>
|
|
761
|
+
): React.FC<P> {
|
|
762
|
+
return function WithErrorBoundaryComponent(props: P) {
|
|
763
|
+
const [hasError, setHasError] = useState(false);
|
|
764
|
+
const [error, setError] = useState<Error | null>(null);
|
|
765
|
+
|
|
766
|
+
useEffect(() => {
|
|
767
|
+
const handleError = (error: ErrorEvent) => {
|
|
768
|
+
setHasError(true);
|
|
769
|
+
setError(new Error(error.message));
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
window.addEventListener('error', handleError);
|
|
773
|
+
return () => window.removeEventListener('error', handleError);
|
|
774
|
+
}, []);
|
|
775
|
+
|
|
776
|
+
if (hasError) {
|
|
777
|
+
return (
|
|
778
|
+
<div className="error-boundary">
|
|
779
|
+
<h2>Something went wrong</h2>
|
|
780
|
+
<p>{error?.message}</p>
|
|
781
|
+
<button onClick={() => {
|
|
782
|
+
setHasError(false);
|
|
783
|
+
setError(null);
|
|
784
|
+
}}>
|
|
785
|
+
Try again
|
|
786
|
+
</button>
|
|
787
|
+
</div>
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
return <WrappedComponent {...props} />;
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
export {
|
|
796
|
+
UserData,
|
|
797
|
+
UserCardProps,
|
|
798
|
+
UserListProps,
|
|
799
|
+
useLocalStorage,
|
|
800
|
+
List,
|
|
801
|
+
UserCard,
|
|
802
|
+
UserList,
|
|
803
|
+
withErrorBoundary,
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
export default UserList;
|
|
807
|
+
"#;
|
|
808
|
+
|
|
809
|
+
fs::write(&test_file, content)?;
|
|
810
|
+
|
|
811
|
+
let ctx = TestContext::new();
|
|
812
|
+
let output = ctx.run_probe(&[
|
|
813
|
+
"search",
|
|
814
|
+
"React", // Search for React-related symbols
|
|
815
|
+
test_file.to_str().unwrap(),
|
|
816
|
+
"--format",
|
|
817
|
+
"outline",
|
|
818
|
+
"--allow-tests",
|
|
819
|
+
"--exact",
|
|
820
|
+
])?;
|
|
821
|
+
|
|
822
|
+
let output2 = ctx.run_probe(&[
|
|
823
|
+
"search",
|
|
824
|
+
"function", // Search for function declarations
|
|
825
|
+
test_file.to_str().unwrap(),
|
|
826
|
+
"--format",
|
|
827
|
+
"outline",
|
|
828
|
+
"--allow-tests",
|
|
829
|
+
"--exact",
|
|
830
|
+
])?;
|
|
831
|
+
|
|
832
|
+
// Verify React component structures with TypeScript
|
|
833
|
+
// For now, just verify we get some results - React content might not be strongly matched
|
|
834
|
+
assert!(
|
|
835
|
+
!output.is_empty() || !output2.is_empty(),
|
|
836
|
+
"Should find some content in React components - output: {} | output2: {}",
|
|
837
|
+
output,
|
|
838
|
+
output2
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
// Test function search
|
|
842
|
+
assert!(
|
|
843
|
+
output2.contains("function"),
|
|
844
|
+
"Missing function declarations - output2: {}",
|
|
845
|
+
output2
|
|
846
|
+
);
|
|
847
|
+
|
|
848
|
+
Ok(())
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
#[test]
|
|
852
|
+
fn test_typescript_outline_test_patterns() -> Result<()> {
|
|
853
|
+
let temp_dir = TempDir::new()?;
|
|
854
|
+
let test_file = temp_dir.path().join("test_patterns.ts");
|
|
855
|
+
|
|
856
|
+
let content = r#"// TypeScript test patterns with Jest and type safety
|
|
857
|
+
|
|
858
|
+
import { Calculator } from './calculator';
|
|
859
|
+
|
|
860
|
+
// Test types and interfaces
|
|
861
|
+
interface TestUser {
|
|
862
|
+
id: string;
|
|
863
|
+
name: string;
|
|
864
|
+
email: string;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
type MockedFunction<T extends (...args: any[]) => any> = jest.MockedFunction<T>;
|
|
868
|
+
|
|
869
|
+
describe('Calculator', () => {
|
|
870
|
+
let calculator: Calculator;
|
|
871
|
+
|
|
872
|
+
beforeEach(() => {
|
|
873
|
+
calculator = new Calculator('Test Calculator');
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
afterEach(() => {
|
|
877
|
+
calculator = null as any;
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
describe('type safety', () => {
|
|
881
|
+
test('should handle number types correctly', () => {
|
|
882
|
+
const result: number = calculator.add(2, 3);
|
|
883
|
+
expect(result).toBe(5);
|
|
884
|
+
expect(typeof result).toBe('number');
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test('should enforce parameter types', () => {
|
|
888
|
+
// TypeScript would catch these errors at compile time
|
|
889
|
+
expect(() => {
|
|
890
|
+
// @ts-ignore - Testing runtime behavior
|
|
891
|
+
calculator.add('2' as any, '3' as any);
|
|
892
|
+
}).toThrow('Invalid input type');
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
describe('generic methods', () => {
|
|
897
|
+
test('should work with generic operations', () => {
|
|
898
|
+
const operations = calculator.getOperations<'add' | 'subtract'>();
|
|
899
|
+
expect(operations).toContain('add');
|
|
900
|
+
expect(operations).toContain('subtract');
|
|
901
|
+
});
|
|
902
|
+
});
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
// Mock with proper TypeScript types
|
|
906
|
+
const mockFetchUser = jest.fn() as MockedFunction<
|
|
907
|
+
(id: string) => Promise<TestUser>
|
|
908
|
+
>;
|
|
909
|
+
|
|
910
|
+
describe('async operations with types', () => {
|
|
911
|
+
beforeEach(() => {
|
|
912
|
+
mockFetchUser.mockClear();
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
test('should fetch user data with proper types', async () => {
|
|
916
|
+
const mockUser: TestUser = {
|
|
917
|
+
id: '123',
|
|
918
|
+
name: 'John Doe',
|
|
919
|
+
email: 'john@example.com',
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
mockFetchUser.mockResolvedValue(mockUser);
|
|
923
|
+
|
|
924
|
+
const result = await mockFetchUser('123');
|
|
925
|
+
|
|
926
|
+
expect(result).toEqual(mockUser);
|
|
927
|
+
expect(result.id).toBe('123');
|
|
928
|
+
expect(typeof result.name).toBe('string');
|
|
929
|
+
|
|
930
|
+
// TypeScript ensures these properties exist
|
|
931
|
+
expect(result.id).toBeDefined();
|
|
932
|
+
expect(result.name).toBeDefined();
|
|
933
|
+
expect(result.email).toBeDefined();
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
test('should handle async errors with proper types', async () => {
|
|
937
|
+
const mockError = new Error('Network error');
|
|
938
|
+
mockFetchUser.mockRejectedValue(mockError);
|
|
939
|
+
|
|
940
|
+
await expect(mockFetchUser('123')).rejects.toThrow('Network error');
|
|
941
|
+
await expect(mockFetchUser('123')).rejects.toBeInstanceOf(Error);
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
// Generic test helper functions
|
|
946
|
+
function createMockUser(overrides: Partial<TestUser> = {}): TestUser {
|
|
947
|
+
return {
|
|
948
|
+
id: '123',
|
|
949
|
+
name: 'Test User',
|
|
950
|
+
email: 'test@example.com',
|
|
951
|
+
...overrides,
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function expectTypeOf<T>(value: T): jest.Matchers<T> {
|
|
956
|
+
return expect(value);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Test class with TypeScript features
|
|
960
|
+
class TestHelpers {
|
|
961
|
+
static createCalculator(precision: number = 2): Calculator {
|
|
962
|
+
return new Calculator('Test', precision);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
static async waitFor<T>(
|
|
966
|
+
condition: () => T | Promise<T>,
|
|
967
|
+
timeout: number = 5000
|
|
968
|
+
): Promise<T> {
|
|
969
|
+
const startTime = Date.now();
|
|
970
|
+
|
|
971
|
+
while (Date.now() - startTime < timeout) {
|
|
972
|
+
try {
|
|
973
|
+
const result = await condition();
|
|
974
|
+
if (result) {
|
|
975
|
+
return result;
|
|
976
|
+
}
|
|
977
|
+
} catch (error) {
|
|
978
|
+
// Continue waiting
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
throw new Error(`Condition not met within ${timeout}ms`);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
static createPartialMock<T>(partial: Partial<T>): T {
|
|
988
|
+
return partial as T;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// Parameterized tests with types
|
|
993
|
+
interface CalculationTestCase {
|
|
994
|
+
a: number;
|
|
995
|
+
b: number;
|
|
996
|
+
expected: number;
|
|
997
|
+
operation: 'add' | 'subtract' | 'multiply' | 'divide';
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const calculationTestCases: CalculationTestCase[] = [
|
|
1001
|
+
{ a: 2, b: 3, expected: 5, operation: 'add' },
|
|
1002
|
+
{ a: 5, b: 2, expected: 3, operation: 'subtract' },
|
|
1003
|
+
{ a: 3, b: 4, expected: 12, operation: 'multiply' },
|
|
1004
|
+
{ a: 8, b: 2, expected: 4, operation: 'divide' },
|
|
1005
|
+
];
|
|
1006
|
+
|
|
1007
|
+
describe.each(calculationTestCases)(
|
|
1008
|
+
'calculator operations',
|
|
1009
|
+
({ a, b, expected, operation }) => {
|
|
1010
|
+
test(`${operation}(${a}, ${b}) should equal ${expected}`, () => {
|
|
1011
|
+
const calculator = TestHelpers.createCalculator();
|
|
1012
|
+
const result = calculator[operation](a, b);
|
|
1013
|
+
expect(result).toBe(expected);
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
// Mock classes with TypeScript
|
|
1019
|
+
class MockUserService {
|
|
1020
|
+
private users: Map<string, TestUser> = new Map();
|
|
1021
|
+
|
|
1022
|
+
async getUser(id: string): Promise<TestUser | null> {
|
|
1023
|
+
return this.users.get(id) || null;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
async createUser(userData: Omit<TestUser, 'id'>): Promise<TestUser> {
|
|
1027
|
+
const user: TestUser = {
|
|
1028
|
+
...userData,
|
|
1029
|
+
id: Math.random().toString(36).substr(2, 9),
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
this.users.set(user.id, user);
|
|
1033
|
+
return user;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
async updateUser(id: string, updates: Partial<TestUser>): Promise<TestUser> {
|
|
1037
|
+
const existing = this.users.get(id);
|
|
1038
|
+
if (!existing) {
|
|
1039
|
+
throw new Error('User not found');
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const updated = { ...existing, ...updates };
|
|
1043
|
+
this.users.set(id, updated);
|
|
1044
|
+
return updated;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
clear(): void {
|
|
1048
|
+
this.users.clear();
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
describe('UserService integration tests', () => {
|
|
1053
|
+
let userService: MockUserService;
|
|
1054
|
+
|
|
1055
|
+
beforeEach(() => {
|
|
1056
|
+
userService = new MockUserService();
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
afterEach(() => {
|
|
1060
|
+
userService.clear();
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
test('should create and retrieve users', async () => {
|
|
1064
|
+
const userData = { name: 'Alice', email: 'alice@example.com' };
|
|
1065
|
+
const createdUser = await userService.createUser(userData);
|
|
1066
|
+
|
|
1067
|
+
expect(createdUser.id).toBeDefined();
|
|
1068
|
+
expect(createdUser.name).toBe(userData.name);
|
|
1069
|
+
expect(createdUser.email).toBe(userData.email);
|
|
1070
|
+
|
|
1071
|
+
const retrievedUser = await userService.getUser(createdUser.id);
|
|
1072
|
+
expect(retrievedUser).toEqual(createdUser);
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
test('should update user data', async () => {
|
|
1076
|
+
const user = await userService.createUser({
|
|
1077
|
+
name: 'Bob',
|
|
1078
|
+
email: 'bob@example.com',
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
const updates = { name: 'Robert' };
|
|
1082
|
+
const updatedUser = await userService.updateUser(user.id, updates);
|
|
1083
|
+
|
|
1084
|
+
expect(updatedUser.name).toBe('Robert');
|
|
1085
|
+
expect(updatedUser.email).toBe('bob@example.com');
|
|
1086
|
+
expect(updatedUser.id).toBe(user.id);
|
|
1087
|
+
});
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
export {
|
|
1091
|
+
TestUser,
|
|
1092
|
+
MockedFunction,
|
|
1093
|
+
createMockUser,
|
|
1094
|
+
expectTypeOf,
|
|
1095
|
+
TestHelpers,
|
|
1096
|
+
CalculationTestCase,
|
|
1097
|
+
MockUserService,
|
|
1098
|
+
};
|
|
1099
|
+
"#;
|
|
1100
|
+
|
|
1101
|
+
fs::write(&test_file, content)?;
|
|
1102
|
+
|
|
1103
|
+
let ctx = TestContext::new();
|
|
1104
|
+
let output = ctx.run_probe(&[
|
|
1105
|
+
"search",
|
|
1106
|
+
"test", // Search for test-related symbols
|
|
1107
|
+
test_file.to_str().unwrap(),
|
|
1108
|
+
"--format",
|
|
1109
|
+
"outline",
|
|
1110
|
+
"--allow-tests",
|
|
1111
|
+
"--exact",
|
|
1112
|
+
])?;
|
|
1113
|
+
|
|
1114
|
+
let output2 = ctx.run_probe(&[
|
|
1115
|
+
"search",
|
|
1116
|
+
"function", // Search for function declarations
|
|
1117
|
+
test_file.to_str().unwrap(),
|
|
1118
|
+
"--format",
|
|
1119
|
+
"outline",
|
|
1120
|
+
"--allow-tests",
|
|
1121
|
+
"--exact",
|
|
1122
|
+
])?;
|
|
1123
|
+
|
|
1124
|
+
// Verify TypeScript test patterns - should find describe blocks or test functions
|
|
1125
|
+
assert!(
|
|
1126
|
+
output.contains("test") || output.contains("describe"),
|
|
1127
|
+
"Should find test-related symbols - output: {}",
|
|
1128
|
+
output
|
|
1129
|
+
);
|
|
1130
|
+
|
|
1131
|
+
// Test function search
|
|
1132
|
+
assert!(
|
|
1133
|
+
output2.contains("function"),
|
|
1134
|
+
"Missing function declarations - output2: {}",
|
|
1135
|
+
output2
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
Ok(())
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
#[test]
|
|
1142
|
+
fn test_typescript_outline_large_function_closing_braces() -> Result<()> {
|
|
1143
|
+
let temp_dir = TempDir::new()?;
|
|
1144
|
+
let test_file = temp_dir.path().join("large_function.ts");
|
|
1145
|
+
|
|
1146
|
+
let content = r#"/**
|
|
1147
|
+
* Large TypeScript function with complex types and nested blocks.
|
|
1148
|
+
*/
|
|
1149
|
+
function complexDataProcessor<T extends { value: number; category?: string }>(
|
|
1150
|
+
data: T[],
|
|
1151
|
+
options: {
|
|
1152
|
+
threshold: number;
|
|
1153
|
+
categorizer?: (item: T) => string;
|
|
1154
|
+
validator?: (item: T) => boolean;
|
|
1155
|
+
}
|
|
1156
|
+
): { processed: T[]; summary: Record<string, number> } {
|
|
1157
|
+
const results: T[] = [];
|
|
1158
|
+
const categories = new Map<string, T[]>();
|
|
1159
|
+
const summary: Record<string, number> = {};
|
|
1160
|
+
|
|
1161
|
+
// Phase 1: Validation and categorization
|
|
1162
|
+
for (const [index, item] of data.entries()) {
|
|
1163
|
+
// Validation phase
|
|
1164
|
+
if (options.validator) {
|
|
1165
|
+
if (!options.validator(item)) {
|
|
1166
|
+
console.warn(`Item at index ${index} failed validation:`, item);
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// Default validation
|
|
1172
|
+
if (typeof item.value !== 'number' || isNaN(item.value)) {
|
|
1173
|
+
console.warn(`Item at index ${index} has invalid value:`, item);
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// Categorization
|
|
1178
|
+
let category: string;
|
|
1179
|
+
if (options.categorizer) {
|
|
1180
|
+
category = options.categorizer(item);
|
|
1181
|
+
} else {
|
|
1182
|
+
if (item.value < 0) {
|
|
1183
|
+
category = 'negative';
|
|
1184
|
+
} else if (item.value === 0) {
|
|
1185
|
+
category = 'zero';
|
|
1186
|
+
} else if (item.value < options.threshold) {
|
|
1187
|
+
category = 'below_threshold';
|
|
1188
|
+
} else {
|
|
1189
|
+
category = 'above_threshold';
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Store in category map
|
|
1194
|
+
if (!categories.has(category)) {
|
|
1195
|
+
categories.set(category, []);
|
|
1196
|
+
}
|
|
1197
|
+
categories.get(category)!.push(item);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// Phase 2: Processing each category
|
|
1201
|
+
for (const [categoryName, items] of categories) {
|
|
1202
|
+
summary[categoryName] = items.length;
|
|
1203
|
+
|
|
1204
|
+
switch (categoryName) {
|
|
1205
|
+
case 'negative':
|
|
1206
|
+
for (const item of items) {
|
|
1207
|
+
const processedItem: T = {
|
|
1208
|
+
...item,
|
|
1209
|
+
value: Math.abs(item.value),
|
|
1210
|
+
category: 'processed_negative',
|
|
1211
|
+
} as T;
|
|
1212
|
+
results.push(processedItem);
|
|
1213
|
+
}
|
|
1214
|
+
break;
|
|
1215
|
+
|
|
1216
|
+
case 'zero':
|
|
1217
|
+
for (const item of items) {
|
|
1218
|
+
const processedItem: T = {
|
|
1219
|
+
...item,
|
|
1220
|
+
value: 0.1, // Convert zero to small positive
|
|
1221
|
+
category: 'processed_zero',
|
|
1222
|
+
} as T;
|
|
1223
|
+
results.push(processedItem);
|
|
1224
|
+
}
|
|
1225
|
+
break;
|
|
1226
|
+
|
|
1227
|
+
case 'below_threshold':
|
|
1228
|
+
for (const item of items) {
|
|
1229
|
+
const multiplier = options.threshold / item.value;
|
|
1230
|
+
const processedItem: T = {
|
|
1231
|
+
...item,
|
|
1232
|
+
value: item.value * multiplier,
|
|
1233
|
+
category: 'normalized',
|
|
1234
|
+
} as T;
|
|
1235
|
+
results.push(processedItem);
|
|
1236
|
+
}
|
|
1237
|
+
break;
|
|
1238
|
+
|
|
1239
|
+
case 'above_threshold':
|
|
1240
|
+
for (const item of items) {
|
|
1241
|
+
const processedItem: T = {
|
|
1242
|
+
...item,
|
|
1243
|
+
category: 'above_threshold',
|
|
1244
|
+
} as T;
|
|
1245
|
+
results.push(processedItem);
|
|
1246
|
+
}
|
|
1247
|
+
break;
|
|
1248
|
+
|
|
1249
|
+
default:
|
|
1250
|
+
// Custom category processing
|
|
1251
|
+
for (const item of items) {
|
|
1252
|
+
const processedItem: T = {
|
|
1253
|
+
...item,
|
|
1254
|
+
category: `custom_${categoryName}`,
|
|
1255
|
+
} as T;
|
|
1256
|
+
results.push(processedItem);
|
|
1257
|
+
}
|
|
1258
|
+
break;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// Phase 3: Final sorting and validation
|
|
1263
|
+
results.sort((a, b) => {
|
|
1264
|
+
if (a.category !== b.category) {
|
|
1265
|
+
return a.category!.localeCompare(b.category!);
|
|
1266
|
+
}
|
|
1267
|
+
return a.value - b.value;
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
// Final validation pass
|
|
1271
|
+
const validatedResults: T[] = [];
|
|
1272
|
+
for (const result of results) {
|
|
1273
|
+
if (result.value > 0 && result.category) {
|
|
1274
|
+
validatedResults.push(result);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
return {
|
|
1279
|
+
processed: validatedResults,
|
|
1280
|
+
summary,
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
"#;
|
|
1284
|
+
|
|
1285
|
+
fs::write(&test_file, content)?;
|
|
1286
|
+
|
|
1287
|
+
let ctx = TestContext::new();
|
|
1288
|
+
let output = ctx.run_probe(&[
|
|
1289
|
+
"search",
|
|
1290
|
+
"complexDataProcessor", // Search for the specific large function
|
|
1291
|
+
test_file.to_str().unwrap(),
|
|
1292
|
+
"--format",
|
|
1293
|
+
"outline",
|
|
1294
|
+
"--allow-tests",
|
|
1295
|
+
"--exact",
|
|
1296
|
+
])?;
|
|
1297
|
+
|
|
1298
|
+
// Verify large function is shown with closing braces
|
|
1299
|
+
assert!(
|
|
1300
|
+
output.contains("complexDataProcessor"),
|
|
1301
|
+
"Missing complexDataProcessor function - output: {}",
|
|
1302
|
+
output
|
|
1303
|
+
);
|
|
1304
|
+
|
|
1305
|
+
// Should have closing braces for large blocks (if the function is found and formatted)
|
|
1306
|
+
if output.contains("complexDataProcessor") {
|
|
1307
|
+
let closing_braces_count = output.matches("}").count();
|
|
1308
|
+
assert!(
|
|
1309
|
+
closing_braces_count >= 1,
|
|
1310
|
+
"Should have closing braces for nested blocks - output: {}",
|
|
1311
|
+
output
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
Ok(())
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
#[test]
|
|
1319
|
+
fn test_typescript_outline_search_command() -> Result<()> {
|
|
1320
|
+
let temp_dir = TempDir::new()?;
|
|
1321
|
+
let test_file = temp_dir.path().join("search_test.ts");
|
|
1322
|
+
|
|
1323
|
+
let content = r#"interface DataProcessor {
|
|
1324
|
+
processData<T>(data: T[]): T[];
|
|
1325
|
+
getProcessedCount(): number;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
class AdvancedDataProcessor implements DataProcessor {
|
|
1329
|
+
private processedCount: number = 0;
|
|
1330
|
+
|
|
1331
|
+
processData<T>(data: T[]): T[] {
|
|
1332
|
+
this.processedCount++;
|
|
1333
|
+
return data.filter(item => item !== null && item !== undefined);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
getProcessedCount(): number {
|
|
1337
|
+
return this.processedCount;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
function processFile(filename: string): Promise<string> {
|
|
1342
|
+
return Promise.resolve(`Processed ${filename}`);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
async function processAsync<T>(data: T): Promise<{ processed: boolean } & T> {
|
|
1346
|
+
return { processed: true, ...data };
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function testDataProcessing(): void {
|
|
1350
|
+
const processor: DataProcessor = new AdvancedDataProcessor();
|
|
1351
|
+
const result = processor.processData([1, 2, null, 3]);
|
|
1352
|
+
console.assert(result.length === 3);
|
|
1353
|
+
}
|
|
1354
|
+
"#;
|
|
1355
|
+
|
|
1356
|
+
fs::write(&test_file, content)?;
|
|
1357
|
+
|
|
1358
|
+
let ctx = TestContext::new();
|
|
1359
|
+
let output = ctx.run_probe(&[
|
|
1360
|
+
"search",
|
|
1361
|
+
"process",
|
|
1362
|
+
temp_dir.path().to_str().unwrap(),
|
|
1363
|
+
"--format",
|
|
1364
|
+
"outline",
|
|
1365
|
+
"--allow-tests",
|
|
1366
|
+
])?;
|
|
1367
|
+
|
|
1368
|
+
// Should find symbols containing "process"
|
|
1369
|
+
assert!(
|
|
1370
|
+
output.contains("DataProcessor")
|
|
1371
|
+
|| output.contains("processData")
|
|
1372
|
+
|| output.contains("processFile")
|
|
1373
|
+
|| output.contains("processAsync"),
|
|
1374
|
+
"Should find process-related symbols - output: {}",
|
|
1375
|
+
output
|
|
1376
|
+
);
|
|
1377
|
+
|
|
1378
|
+
Ok(())
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
#[test]
|
|
1382
|
+
fn test_typescript_outline_small_function_no_closing_braces() -> Result<()> {
|
|
1383
|
+
let temp_dir = TempDir::new()?;
|
|
1384
|
+
let test_file = temp_dir.path().join("small_functions.ts");
|
|
1385
|
+
|
|
1386
|
+
let content = r#"/**
|
|
1387
|
+
* Small TypeScript functions that should NOT get closing braces.
|
|
1388
|
+
*/
|
|
1389
|
+
interface Point {
|
|
1390
|
+
x: number;
|
|
1391
|
+
y: number;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
type Vector = {
|
|
1395
|
+
start: Point;
|
|
1396
|
+
end: Point;
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1399
|
+
function add(a: number, b: number): number {
|
|
1400
|
+
return a + b;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
function multiply(x: number, y: number): number {
|
|
1404
|
+
return x * y;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
const subtract = (a: number, b: number): number => {
|
|
1408
|
+
return a - b;
|
|
1409
|
+
};
|
|
1410
|
+
|
|
1411
|
+
const divide: (x: number, y: number) => number = (x, y) => {
|
|
1412
|
+
if (y === 0) throw new Error('Division by zero');
|
|
1413
|
+
return x / y;
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
function getDistance(p1: Point, p2: Point): number {
|
|
1417
|
+
const dx = p2.x - p1.x;
|
|
1418
|
+
const dy = p2.y - p1.y;
|
|
1419
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
async function fetchData<T>(url: string): Promise<T> {
|
|
1423
|
+
const response = await fetch(url);
|
|
1424
|
+
return response.json();
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
class SmallClass {
|
|
1428
|
+
private value: number;
|
|
1429
|
+
|
|
1430
|
+
constructor(value: number) {
|
|
1431
|
+
this.value = value;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
getValue(): number {
|
|
1435
|
+
return this.value;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
"#;
|
|
1439
|
+
|
|
1440
|
+
fs::write(&test_file, content)?;
|
|
1441
|
+
|
|
1442
|
+
let ctx = TestContext::new();
|
|
1443
|
+
let output = ctx.run_probe(&[
|
|
1444
|
+
"search",
|
|
1445
|
+
"function", // Search for function declarations
|
|
1446
|
+
test_file.to_str().unwrap(),
|
|
1447
|
+
"--format",
|
|
1448
|
+
"outline",
|
|
1449
|
+
"--allow-tests",
|
|
1450
|
+
"--exact",
|
|
1451
|
+
])?;
|
|
1452
|
+
|
|
1453
|
+
// Verify small functions are found but don't have excessive closing braces
|
|
1454
|
+
assert!(
|
|
1455
|
+
output.contains("function") || output.contains("add") || output.contains("multiply"),
|
|
1456
|
+
"Should find small functions - output: {}",
|
|
1457
|
+
output
|
|
1458
|
+
);
|
|
1459
|
+
|
|
1460
|
+
// Small functions should have minimal closing braces (ideally none for outline)
|
|
1461
|
+
let closing_braces_count = output.matches("} //").count(); // closing brace comments
|
|
1462
|
+
assert!(
|
|
1463
|
+
closing_braces_count <= 2, // Allow some, but not excessive
|
|
1464
|
+
"Small functions should not have many closing brace comments - output: {}",
|
|
1465
|
+
output
|
|
1466
|
+
);
|
|
1467
|
+
|
|
1468
|
+
Ok(())
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
#[test]
|
|
1472
|
+
fn test_typescript_outline_keyword_highlighting() -> Result<()> {
|
|
1473
|
+
let temp_dir = TempDir::new()?;
|
|
1474
|
+
let test_file = temp_dir.path().join("keyword_highlighting.ts");
|
|
1475
|
+
|
|
1476
|
+
let content = r#"// TypeScript with various keywords for highlighting tests
|
|
1477
|
+
interface DatabaseConfig {
|
|
1478
|
+
host: string;
|
|
1479
|
+
port: number;
|
|
1480
|
+
database: string;
|
|
1481
|
+
username: string;
|
|
1482
|
+
password: string;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
type ConnectionStatus = 'connected' | 'disconnected' | 'connecting' | 'error';
|
|
1486
|
+
|
|
1487
|
+
enum LogLevel {
|
|
1488
|
+
DEBUG = 'debug',
|
|
1489
|
+
INFO = 'info',
|
|
1490
|
+
WARN = 'warn',
|
|
1491
|
+
ERROR = 'error',
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
abstract class BaseLogger {
|
|
1495
|
+
protected level: LogLevel;
|
|
1496
|
+
|
|
1497
|
+
constructor(level: LogLevel = LogLevel.INFO) {
|
|
1498
|
+
this.level = level;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
abstract log(message: string, level: LogLevel): void;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
class ConsoleLogger extends BaseLogger {
|
|
1505
|
+
log(message: string, level: LogLevel): void {
|
|
1506
|
+
if (this.shouldLog(level)) {
|
|
1507
|
+
console.log(`[${level}] ${message}`);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
private shouldLog(level: LogLevel): boolean {
|
|
1512
|
+
const levels = Object.values(LogLevel);
|
|
1513
|
+
return levels.indexOf(level) >= levels.indexOf(this.level);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function createLogger(config: DatabaseConfig): BaseLogger {
|
|
1518
|
+
return new ConsoleLogger();
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
async function connectDatabase(config: DatabaseConfig): Promise<ConnectionStatus> {
|
|
1522
|
+
try {
|
|
1523
|
+
// Simulate connection logic
|
|
1524
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1525
|
+
return 'connected';
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
return 'error';
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// Generic function with constraints
|
|
1532
|
+
function processData<T extends { id: string }>(data: T[]): T[] {
|
|
1533
|
+
return data.filter(item => item.id && item.id.length > 0);
|
|
1534
|
+
}
|
|
1535
|
+
"#;
|
|
1536
|
+
|
|
1537
|
+
fs::write(&test_file, content)?;
|
|
1538
|
+
|
|
1539
|
+
let ctx = TestContext::new();
|
|
1540
|
+
|
|
1541
|
+
// Test various TypeScript keywords
|
|
1542
|
+
for keyword in [
|
|
1543
|
+
"interface",
|
|
1544
|
+
"type",
|
|
1545
|
+
"enum",
|
|
1546
|
+
"abstract",
|
|
1547
|
+
"extends",
|
|
1548
|
+
"async",
|
|
1549
|
+
"function",
|
|
1550
|
+
] {
|
|
1551
|
+
let output = ctx.run_probe(&[
|
|
1552
|
+
"search",
|
|
1553
|
+
keyword,
|
|
1554
|
+
test_file.to_str().unwrap(),
|
|
1555
|
+
"--format",
|
|
1556
|
+
"outline",
|
|
1557
|
+
"--allow-tests",
|
|
1558
|
+
])?;
|
|
1559
|
+
|
|
1560
|
+
// Should find the keyword in outline format
|
|
1561
|
+
assert!(
|
|
1562
|
+
output.contains(keyword) || !output.is_empty(),
|
|
1563
|
+
"Should find keyword '{}' - output: {}",
|
|
1564
|
+
keyword,
|
|
1565
|
+
output
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
Ok(())
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
#[test]
|
|
1573
|
+
fn test_typescript_outline_array_object_truncation() -> Result<()> {
|
|
1574
|
+
let temp_dir = TempDir::new()?;
|
|
1575
|
+
let test_file = temp_dir.path().join("array_object_truncation.ts");
|
|
1576
|
+
|
|
1577
|
+
let content = r#"// TypeScript with large arrays and objects for truncation testing
|
|
1578
|
+
interface UserProfile {
|
|
1579
|
+
id: string;
|
|
1580
|
+
name: string;
|
|
1581
|
+
email: string;
|
|
1582
|
+
preferences: {
|
|
1583
|
+
theme: 'light' | 'dark';
|
|
1584
|
+
language: string;
|
|
1585
|
+
notifications: boolean;
|
|
1586
|
+
keywords: string[];
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
const largeArray: string[] = [
|
|
1591
|
+
'first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth',
|
|
1592
|
+
'ninth', 'tenth', 'eleventh', 'twelfth', 'thirteenth', 'fourteenth', 'fifteenth',
|
|
1593
|
+
'sixteenth', 'seventeenth', 'eighteenth', 'nineteenth', 'twentieth', 'keyword',
|
|
1594
|
+
'twenty-first', 'twenty-second', 'twenty-third', 'twenty-fourth', 'twenty-fifth',
|
|
1595
|
+
];
|
|
1596
|
+
|
|
1597
|
+
const complexObject: UserProfile = {
|
|
1598
|
+
id: '12345',
|
|
1599
|
+
name: 'John Doe',
|
|
1600
|
+
email: 'john@example.com',
|
|
1601
|
+
preferences: {
|
|
1602
|
+
theme: 'dark',
|
|
1603
|
+
language: 'en',
|
|
1604
|
+
notifications: true,
|
|
1605
|
+
keywords: ['typescript', 'javascript', 'react', 'node', 'keyword', 'development'],
|
|
1606
|
+
},
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
type LargeTypeDefinition = {
|
|
1610
|
+
field1: string;
|
|
1611
|
+
field2: number;
|
|
1612
|
+
field3: boolean;
|
|
1613
|
+
field4: string[];
|
|
1614
|
+
field5: {
|
|
1615
|
+
nested1: string;
|
|
1616
|
+
nested2: number;
|
|
1617
|
+
nested3: boolean;
|
|
1618
|
+
nested4: string;
|
|
1619
|
+
nested5: number;
|
|
1620
|
+
keyword: string;
|
|
1621
|
+
};
|
|
1622
|
+
field6: Date;
|
|
1623
|
+
field7: RegExp;
|
|
1624
|
+
field8: Function;
|
|
1625
|
+
field9: any;
|
|
1626
|
+
field10: unknown;
|
|
1627
|
+
};
|
|
1628
|
+
|
|
1629
|
+
function processLargeData(data: LargeTypeDefinition[]): LargeTypeDefinition[] {
|
|
1630
|
+
return data.filter(item =>
|
|
1631
|
+
item.field1 &&
|
|
1632
|
+
item.field2 > 0 &&
|
|
1633
|
+
item.field3 !== null &&
|
|
1634
|
+
item.field4.length > 0 &&
|
|
1635
|
+
item.field5.keyword.includes('keyword')
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
"#;
|
|
1639
|
+
|
|
1640
|
+
fs::write(&test_file, content)?;
|
|
1641
|
+
|
|
1642
|
+
let ctx = TestContext::new();
|
|
1643
|
+
let output = ctx.run_probe(&[
|
|
1644
|
+
"search",
|
|
1645
|
+
"keyword", // Search for keyword that should be preserved even in truncated arrays/objects
|
|
1646
|
+
test_file.to_str().unwrap(),
|
|
1647
|
+
"--format",
|
|
1648
|
+
"outline",
|
|
1649
|
+
"--allow-tests",
|
|
1650
|
+
"--exact",
|
|
1651
|
+
])?;
|
|
1652
|
+
|
|
1653
|
+
// Should find the keyword even in truncated content
|
|
1654
|
+
assert!(
|
|
1655
|
+
output.contains("keyword"),
|
|
1656
|
+
"Should preserve keyword in truncated content - output: {}",
|
|
1657
|
+
output
|
|
1658
|
+
);
|
|
1659
|
+
|
|
1660
|
+
let output2 = ctx.run_probe(&[
|
|
1661
|
+
"search",
|
|
1662
|
+
"largeArray",
|
|
1663
|
+
test_file.to_str().unwrap(),
|
|
1664
|
+
"--format",
|
|
1665
|
+
"outline",
|
|
1666
|
+
"--allow-tests",
|
|
1667
|
+
"--exact",
|
|
1668
|
+
])?;
|
|
1669
|
+
|
|
1670
|
+
// Should contain array/object content, possibly with truncation
|
|
1671
|
+
assert!(
|
|
1672
|
+
output2.contains("largeArray") || output2.contains("string[]"),
|
|
1673
|
+
"Should find large array definition - output2: {}",
|
|
1674
|
+
output2
|
|
1675
|
+
);
|
|
1676
|
+
|
|
1677
|
+
Ok(())
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
#[test]
|
|
1681
|
+
fn test_typescript_outline_advanced_features() -> Result<()> {
|
|
1682
|
+
let temp_dir = TempDir::new()?;
|
|
1683
|
+
let test_file = temp_dir.path().join("advanced_features.ts");
|
|
1684
|
+
|
|
1685
|
+
let content = r#"// Advanced TypeScript features: decorators, utility types, conditional types
|
|
1686
|
+
import { Component } from '@angular/core';
|
|
1687
|
+
|
|
1688
|
+
// Decorators
|
|
1689
|
+
@Component({
|
|
1690
|
+
selector: 'app-user',
|
|
1691
|
+
template: '<div>{{user.name}}</div>'
|
|
1692
|
+
})
|
|
1693
|
+
class UserComponent {
|
|
1694
|
+
@Input() user: User;
|
|
1695
|
+
|
|
1696
|
+
@Output() userSelected = new EventEmitter<User>();
|
|
1697
|
+
|
|
1698
|
+
@HostListener('click', ['$event'])
|
|
1699
|
+
onClick(event: MouseEvent): void {
|
|
1700
|
+
this.userSelected.emit(this.user);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// Utility types
|
|
1705
|
+
type PartialUser = Partial<User>;
|
|
1706
|
+
type RequiredUser = Required<User>;
|
|
1707
|
+
type PickedUser = Pick<User, 'id' | 'name'>;
|
|
1708
|
+
type OmittedUser = Omit<User, 'password'>;
|
|
1709
|
+
type RecordType = Record<string, number>;
|
|
1710
|
+
|
|
1711
|
+
// Conditional types
|
|
1712
|
+
type NonNullable<T> = T extends null | undefined ? never : T;
|
|
1713
|
+
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
|
|
1714
|
+
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
|
|
1715
|
+
|
|
1716
|
+
// Mapped types
|
|
1717
|
+
type Readonly<T> = {
|
|
1718
|
+
readonly [P in keyof T]: T[P];
|
|
1719
|
+
};
|
|
1720
|
+
|
|
1721
|
+
type Optional<T> = {
|
|
1722
|
+
[P in keyof T]?: T[P];
|
|
1723
|
+
};
|
|
1724
|
+
|
|
1725
|
+
// Template literal types
|
|
1726
|
+
type EventName<T extends string> = `on${Capitalize<T>}`;
|
|
1727
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
1728
|
+
type ApiEndpoint<T extends HttpMethod> = `/${Lowercase<T>}/api`;
|
|
1729
|
+
|
|
1730
|
+
// Advanced generic constraints
|
|
1731
|
+
interface Identifiable {
|
|
1732
|
+
id: string;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
interface Timestamped {
|
|
1736
|
+
createdAt: Date;
|
|
1737
|
+
updatedAt: Date;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
type Entity<T> = T & Identifiable & Timestamped;
|
|
1741
|
+
|
|
1742
|
+
class Repository<T extends Identifiable> {
|
|
1743
|
+
private items: Map<string, T> = new Map();
|
|
1744
|
+
|
|
1745
|
+
save(item: T): void {
|
|
1746
|
+
this.items.set(item.id, item);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
findById(id: string): T | undefined {
|
|
1750
|
+
return this.items.get(id);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
findAll(): T[] {
|
|
1754
|
+
return Array.from(this.items.values());
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// Function overloads
|
|
1759
|
+
function createElement(tagName: 'div'): HTMLDivElement;
|
|
1760
|
+
function createElement(tagName: 'span'): HTMLSpanElement;
|
|
1761
|
+
function createElement(tagName: 'input'): HTMLInputElement;
|
|
1762
|
+
function createElement(tagName: string): HTMLElement;
|
|
1763
|
+
function createElement(tagName: string): HTMLElement {
|
|
1764
|
+
return document.createElement(tagName);
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// Namespace and module augmentation
|
|
1768
|
+
namespace Utils {
|
|
1769
|
+
export function formatDate(date: Date): string {
|
|
1770
|
+
return date.toISOString().split('T')[0];
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
export namespace Math {
|
|
1774
|
+
export function clamp(value: number, min: number, max: number): number {
|
|
1775
|
+
return Math.min(Math.max(value, min), max);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// Module augmentation
|
|
1781
|
+
declare global {
|
|
1782
|
+
interface Window {
|
|
1783
|
+
customProperty: string;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
declare module 'express' {
|
|
1788
|
+
interface Request {
|
|
1789
|
+
user?: User;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
"#;
|
|
1793
|
+
|
|
1794
|
+
fs::write(&test_file, content)?;
|
|
1795
|
+
|
|
1796
|
+
let ctx = TestContext::new();
|
|
1797
|
+
|
|
1798
|
+
// Test decorator patterns
|
|
1799
|
+
let output1 = ctx.run_probe(&[
|
|
1800
|
+
"search",
|
|
1801
|
+
"Component",
|
|
1802
|
+
test_file.to_str().unwrap(),
|
|
1803
|
+
"--format",
|
|
1804
|
+
"outline",
|
|
1805
|
+
"--allow-tests",
|
|
1806
|
+
"--exact",
|
|
1807
|
+
])?;
|
|
1808
|
+
|
|
1809
|
+
// Test utility types
|
|
1810
|
+
let output2 = ctx.run_probe(&[
|
|
1811
|
+
"search",
|
|
1812
|
+
"Partial",
|
|
1813
|
+
test_file.to_str().unwrap(),
|
|
1814
|
+
"--format",
|
|
1815
|
+
"outline",
|
|
1816
|
+
"--allow-tests",
|
|
1817
|
+
"--exact",
|
|
1818
|
+
])?;
|
|
1819
|
+
|
|
1820
|
+
// Test conditional types
|
|
1821
|
+
let output3 = ctx.run_probe(&[
|
|
1822
|
+
"search",
|
|
1823
|
+
"extends",
|
|
1824
|
+
test_file.to_str().unwrap(),
|
|
1825
|
+
"--format",
|
|
1826
|
+
"outline",
|
|
1827
|
+
"--allow-tests",
|
|
1828
|
+
])?;
|
|
1829
|
+
|
|
1830
|
+
// Should find advanced TypeScript features
|
|
1831
|
+
assert!(
|
|
1832
|
+
!output1.is_empty() || !output2.is_empty() || !output3.is_empty(),
|
|
1833
|
+
"Should find advanced TypeScript features - output1: {} | output2: {} | output3: {}",
|
|
1834
|
+
output1,
|
|
1835
|
+
output2,
|
|
1836
|
+
output3
|
|
1837
|
+
);
|
|
1838
|
+
|
|
1839
|
+
Ok(())
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
#[test]
|
|
1843
|
+
fn test_typescript_outline_control_flow_statements() -> Result<()> {
|
|
1844
|
+
let temp_dir = TempDir::new()?;
|
|
1845
|
+
let test_file = temp_dir.path().join("control_flow.ts");
|
|
1846
|
+
|
|
1847
|
+
let content = r#"/**
|
|
1848
|
+
* TypeScript control flow structures for testing outline format.
|
|
1849
|
+
*/
|
|
1850
|
+
interface User {
|
|
1851
|
+
id: string;
|
|
1852
|
+
name: string;
|
|
1853
|
+
age: number;
|
|
1854
|
+
role: 'admin' | 'user' | 'guest';
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
function validateUser(user: User): boolean {
|
|
1858
|
+
// Simple if statement
|
|
1859
|
+
if (!user.id || user.id.length === 0) {
|
|
1860
|
+
return false;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// Switch statement with TypeScript types
|
|
1864
|
+
switch (user.role) {
|
|
1865
|
+
case 'admin':
|
|
1866
|
+
return user.age >= 21;
|
|
1867
|
+
case 'user':
|
|
1868
|
+
return user.age >= 13;
|
|
1869
|
+
case 'guest':
|
|
1870
|
+
return user.age >= 0;
|
|
1871
|
+
default:
|
|
1872
|
+
return false;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
function processUsers(users: User[]): User[] {
|
|
1877
|
+
const validUsers: User[] = [];
|
|
1878
|
+
|
|
1879
|
+
// For loop with TypeScript
|
|
1880
|
+
for (let i = 0; i < users.length; i++) {
|
|
1881
|
+
const user = users[i];
|
|
1882
|
+
|
|
1883
|
+
// Nested if-else
|
|
1884
|
+
if (validateUser(user)) {
|
|
1885
|
+
if (user.role === 'admin') {
|
|
1886
|
+
// Complex nested logic
|
|
1887
|
+
if (user.age > 30) {
|
|
1888
|
+
validUsers.push({ ...user, role: 'admin' });
|
|
1889
|
+
} else {
|
|
1890
|
+
validUsers.push({ ...user, role: 'user' });
|
|
1891
|
+
}
|
|
1892
|
+
} else {
|
|
1893
|
+
validUsers.push(user);
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
return validUsers;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
async function fetchAndProcessUsers(): Promise<User[]> {
|
|
1902
|
+
try {
|
|
1903
|
+
const response = await fetch('/api/users');
|
|
1904
|
+
const users: User[] = await response.json();
|
|
1905
|
+
|
|
1906
|
+
// While loop
|
|
1907
|
+
let retries = 3;
|
|
1908
|
+
while (retries > 0 && users.length === 0) {
|
|
1909
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
1910
|
+
retries--;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
return processUsers(users);
|
|
1914
|
+
} catch (error) {
|
|
1915
|
+
console.error('Failed to fetch users:', error);
|
|
1916
|
+
return [];
|
|
1917
|
+
} finally {
|
|
1918
|
+
console.log('User fetch operation completed');
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
class UserManager {
|
|
1923
|
+
private users: Map<string, User> = new Map();
|
|
1924
|
+
|
|
1925
|
+
addUser(user: User): void {
|
|
1926
|
+
// TypeScript type guards
|
|
1927
|
+
if (this.isValidUser(user)) {
|
|
1928
|
+
this.users.set(user.id, user);
|
|
1929
|
+
} else {
|
|
1930
|
+
throw new Error('Invalid user');
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
private isValidUser(user: any): user is User {
|
|
1935
|
+
return (
|
|
1936
|
+
typeof user === 'object' &&
|
|
1937
|
+
typeof user.id === 'string' &&
|
|
1938
|
+
typeof user.name === 'string' &&
|
|
1939
|
+
typeof user.age === 'number' &&
|
|
1940
|
+
['admin', 'user', 'guest'].includes(user.role)
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1943
|
+
|
|
1944
|
+
getUsersByRole(role: User['role']): User[] {
|
|
1945
|
+
const result: User[] = [];
|
|
1946
|
+
|
|
1947
|
+
// For-of loop with Map
|
|
1948
|
+
for (const [id, user] of this.users.entries()) {
|
|
1949
|
+
if (user.role === role) {
|
|
1950
|
+
result.push(user);
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
return result;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
"#;
|
|
1958
|
+
|
|
1959
|
+
fs::write(&test_file, content)?;
|
|
1960
|
+
|
|
1961
|
+
let ctx = TestContext::new();
|
|
1962
|
+
let output = ctx.run_probe(&[
|
|
1963
|
+
"search",
|
|
1964
|
+
"function", // Search for function declarations
|
|
1965
|
+
test_file.to_str().unwrap(),
|
|
1966
|
+
"--format",
|
|
1967
|
+
"outline",
|
|
1968
|
+
"--allow-tests",
|
|
1969
|
+
"--exact",
|
|
1970
|
+
])?;
|
|
1971
|
+
|
|
1972
|
+
// Verify control flow structures are shown with proper formatting
|
|
1973
|
+
assert!(
|
|
1974
|
+
output.contains("function")
|
|
1975
|
+
|| output.contains("validateUser")
|
|
1976
|
+
|| output.contains("processUsers"),
|
|
1977
|
+
"Should find control flow functions - output: {}",
|
|
1978
|
+
output
|
|
1979
|
+
);
|
|
1980
|
+
|
|
1981
|
+
// Test class search
|
|
1982
|
+
let output2 = ctx.run_probe(&[
|
|
1983
|
+
"search",
|
|
1984
|
+
"class", // Search for class declarations
|
|
1985
|
+
test_file.to_str().unwrap(),
|
|
1986
|
+
"--format",
|
|
1987
|
+
"outline",
|
|
1988
|
+
"--allow-tests",
|
|
1989
|
+
"--exact",
|
|
1990
|
+
])?;
|
|
1991
|
+
|
|
1992
|
+
assert!(
|
|
1993
|
+
output2.contains("class") || output2.contains("UserManager"),
|
|
1994
|
+
"Should find class with control flow - output2: {}",
|
|
1995
|
+
output2
|
|
1996
|
+
);
|
|
1997
|
+
|
|
1998
|
+
Ok(())
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
#[test]
|
|
2002
|
+
fn test_typescript_tsx_file_support() -> Result<()> {
|
|
2003
|
+
let temp_dir = TempDir::new()?;
|
|
2004
|
+
let test_file = temp_dir.path().join("react_component.tsx"); // Note: .tsx extension
|
|
2005
|
+
|
|
2006
|
+
let content = r#"import React, { useState, useEffect, FC, ReactElement } from 'react';
|
|
2007
|
+
|
|
2008
|
+
interface Props {
|
|
2009
|
+
title: string;
|
|
2010
|
+
count?: number;
|
|
2011
|
+
onIncrement?: () => void;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
interface State {
|
|
2015
|
+
value: number;
|
|
2016
|
+
loading: boolean;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
// Functional component with TypeScript
|
|
2020
|
+
const Counter: FC<Props> = ({ title, count = 0, onIncrement }): ReactElement => {
|
|
2021
|
+
const [state, setState] = useState<State>({
|
|
2022
|
+
value: count,
|
|
2023
|
+
loading: false,
|
|
2024
|
+
});
|
|
2025
|
+
|
|
2026
|
+
useEffect(() => {
|
|
2027
|
+
setState(prev => ({ ...prev, value: count }));
|
|
2028
|
+
}, [count]);
|
|
2029
|
+
|
|
2030
|
+
const handleIncrement = (): void => {
|
|
2031
|
+
setState(prev => ({ ...prev, loading: true }));
|
|
2032
|
+
|
|
2033
|
+
setTimeout(() => {
|
|
2034
|
+
setState(prev => ({
|
|
2035
|
+
value: prev.value + 1,
|
|
2036
|
+
loading: false,
|
|
2037
|
+
}));
|
|
2038
|
+
onIncrement?.();
|
|
2039
|
+
}, 100);
|
|
2040
|
+
};
|
|
2041
|
+
|
|
2042
|
+
return (
|
|
2043
|
+
<div className="counter">
|
|
2044
|
+
<h2>{title}</h2>
|
|
2045
|
+
<div className="counter-display">
|
|
2046
|
+
Count: {state.loading ? '...' : state.value}
|
|
2047
|
+
</div>
|
|
2048
|
+
<button
|
|
2049
|
+
onClick={handleIncrement}
|
|
2050
|
+
disabled={state.loading}
|
|
2051
|
+
type="button"
|
|
2052
|
+
>
|
|
2053
|
+
{state.loading ? 'Loading...' : 'Increment'}
|
|
2054
|
+
</button>
|
|
2055
|
+
</div>
|
|
2056
|
+
);
|
|
2057
|
+
};
|
|
2058
|
+
|
|
2059
|
+
// Class component with TypeScript
|
|
2060
|
+
interface ClassCounterProps {
|
|
2061
|
+
initialValue: number;
|
|
2062
|
+
step?: number;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
interface ClassCounterState {
|
|
2066
|
+
count: number;
|
|
2067
|
+
history: number[];
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
class ClassCounter extends React.Component<ClassCounterProps, ClassCounterState> {
|
|
2071
|
+
constructor(props: ClassCounterProps) {
|
|
2072
|
+
super(props);
|
|
2073
|
+
this.state = {
|
|
2074
|
+
count: props.initialValue,
|
|
2075
|
+
history: [props.initialValue],
|
|
2076
|
+
};
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
increment = (): void => {
|
|
2080
|
+
const step = this.props.step || 1;
|
|
2081
|
+
this.setState(prevState => ({
|
|
2082
|
+
count: prevState.count + step,
|
|
2083
|
+
history: [...prevState.history, prevState.count + step],
|
|
2084
|
+
}));
|
|
2085
|
+
};
|
|
2086
|
+
|
|
2087
|
+
reset = (): void => {
|
|
2088
|
+
this.setState({
|
|
2089
|
+
count: this.props.initialValue,
|
|
2090
|
+
history: [this.props.initialValue],
|
|
2091
|
+
});
|
|
2092
|
+
};
|
|
2093
|
+
|
|
2094
|
+
render(): ReactElement {
|
|
2095
|
+
const { count, history } = this.state;
|
|
2096
|
+
|
|
2097
|
+
return (
|
|
2098
|
+
<div className="class-counter">
|
|
2099
|
+
<div>Count: {count}</div>
|
|
2100
|
+
<div>History: {history.join(', ')}</div>
|
|
2101
|
+
<button onClick={this.increment}>Increment</button>
|
|
2102
|
+
<button onClick={this.reset}>Reset</button>
|
|
2103
|
+
</div>
|
|
2104
|
+
);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
// Higher-order component with TypeScript
|
|
2109
|
+
function withLoading<P extends object>(
|
|
2110
|
+
WrappedComponent: React.ComponentType<P>
|
|
2111
|
+
): React.FC<P & { loading?: boolean }> {
|
|
2112
|
+
return function WithLoadingComponent({ loading = false, ...props }) {
|
|
2113
|
+
if (loading) {
|
|
2114
|
+
return <div>Loading...</div>;
|
|
2115
|
+
}
|
|
2116
|
+
return <WrappedComponent {...props as P} />;
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
// Custom hook with TypeScript
|
|
2121
|
+
function useCounter(initialValue: number = 0): [number, () => void, () => void] {
|
|
2122
|
+
const [count, setCount] = useState<number>(initialValue);
|
|
2123
|
+
|
|
2124
|
+
const increment = (): void => {
|
|
2125
|
+
setCount(prev => prev + 1);
|
|
2126
|
+
};
|
|
2127
|
+
|
|
2128
|
+
const reset = (): void => {
|
|
2129
|
+
setCount(initialValue);
|
|
2130
|
+
};
|
|
2131
|
+
|
|
2132
|
+
return [count, increment, reset];
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
export { Counter, ClassCounter, withLoading, useCounter };
|
|
2136
|
+
export type { Props, State, ClassCounterProps, ClassCounterState };
|
|
2137
|
+
"#;
|
|
2138
|
+
|
|
2139
|
+
fs::write(&test_file, content)?;
|
|
2140
|
+
|
|
2141
|
+
let ctx = TestContext::new();
|
|
2142
|
+
let output = ctx.run_probe(&[
|
|
2143
|
+
"search",
|
|
2144
|
+
"React", // Search for React-related symbols
|
|
2145
|
+
test_file.to_str().unwrap(),
|
|
2146
|
+
"--format",
|
|
2147
|
+
"outline",
|
|
2148
|
+
"--allow-tests",
|
|
2149
|
+
"--exact",
|
|
2150
|
+
])?;
|
|
2151
|
+
|
|
2152
|
+
let output2 = ctx.run_probe(&[
|
|
2153
|
+
"search",
|
|
2154
|
+
"function", // Search for function declarations
|
|
2155
|
+
test_file.to_str().unwrap(),
|
|
2156
|
+
"--format",
|
|
2157
|
+
"outline",
|
|
2158
|
+
"--allow-tests",
|
|
2159
|
+
"--exact",
|
|
2160
|
+
])?;
|
|
2161
|
+
|
|
2162
|
+
// Verify TSX file support - should handle both TypeScript and JSX
|
|
2163
|
+
assert!(
|
|
2164
|
+
!output.is_empty() || !output2.is_empty(),
|
|
2165
|
+
"Should process .tsx files correctly - output: {} | output2: {}",
|
|
2166
|
+
output,
|
|
2167
|
+
output2
|
|
2168
|
+
);
|
|
2169
|
+
|
|
2170
|
+
// Test component search
|
|
2171
|
+
let output3 = ctx.run_probe(&[
|
|
2172
|
+
"search",
|
|
2173
|
+
"Counter",
|
|
2174
|
+
test_file.to_str().unwrap(),
|
|
2175
|
+
"--format",
|
|
2176
|
+
"outline",
|
|
2177
|
+
"--allow-tests",
|
|
2178
|
+
"--exact",
|
|
2179
|
+
])?;
|
|
2180
|
+
|
|
2181
|
+
assert!(
|
|
2182
|
+
output3.contains("Counter") || !output3.is_empty(),
|
|
2183
|
+
"Should find Counter component in TSX - output3: {}",
|
|
2184
|
+
output3
|
|
2185
|
+
);
|
|
2186
|
+
|
|
2187
|
+
Ok(())
|
|
2188
|
+
}
|