@vybestack/llxprt-code-tools 0.10.0-nightly.260613.1adad3b34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +294 -0
- package/dist/.last_build +0 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.d.ts +29 -0
- package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.js +30 -0
- package/dist/src/__tests__/fixtures/filesystem-tool-fixtures.js.map +1 -0
- package/dist/src/__tests__/fixtures/key-storage-fixtures.d.ts +39 -0
- package/dist/src/__tests__/fixtures/key-storage-fixtures.js +53 -0
- package/dist/src/__tests__/fixtures/key-storage-fixtures.js.map +1 -0
- package/dist/src/__tests__/fixtures/provider-formatting-fixtures.d.ts +140 -0
- package/dist/src/__tests__/fixtures/provider-formatting-fixtures.js +157 -0
- package/dist/src/__tests__/fixtures/provider-formatting-fixtures.js.map +1 -0
- package/dist/src/__tests__/red-test-helpers.d.ts +14 -0
- package/dist/src/__tests__/red-test-helpers.js +18 -0
- package/dist/src/__tests__/red-test-helpers.js.map +1 -0
- package/dist/src/formatters/IToolFormatter.d.ts +84 -0
- package/dist/src/formatters/IToolFormatter.js +6 -0
- package/dist/src/formatters/IToolFormatter.js.map +1 -0
- package/dist/src/formatters/ToolFormatter.d.ts +94 -0
- package/dist/src/formatters/ToolFormatter.js +379 -0
- package/dist/src/formatters/ToolFormatter.js.map +1 -0
- package/dist/src/formatters/ToolIdStrategy.d.ts +79 -0
- package/dist/src/formatters/ToolIdStrategy.js +173 -0
- package/dist/src/formatters/ToolIdStrategy.js.map +1 -0
- package/dist/src/formatters/doubleEscapeUtils.d.ts +46 -0
- package/dist/src/formatters/doubleEscapeUtils.js +223 -0
- package/dist/src/formatters/doubleEscapeUtils.js.map +1 -0
- package/dist/src/formatters/index.d.ts +21 -0
- package/dist/src/formatters/index.js +12 -0
- package/dist/src/formatters/index.js.map +1 -0
- package/dist/src/formatters/toolIdNormalization.d.ts +28 -0
- package/dist/src/formatters/toolIdNormalization.js +97 -0
- package/dist/src/formatters/toolIdNormalization.js.map +1 -0
- package/dist/src/formatters/toolNameUtils.d.ts +43 -0
- package/dist/src/formatters/toolNameUtils.js +143 -0
- package/dist/src/formatters/toolNameUtils.js.map +1 -0
- package/dist/src/index.d.ts +88 -0
- package/dist/src/index.js +68 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interfaces/IAsyncTaskService.d.ts +57 -0
- package/dist/src/interfaces/IAsyncTaskService.js +6 -0
- package/dist/src/interfaces/IAsyncTaskService.js.map +1 -0
- package/dist/src/interfaces/IIdeService.d.ts +62 -0
- package/dist/src/interfaces/IIdeService.js +6 -0
- package/dist/src/interfaces/IIdeService.js.map +1 -0
- package/dist/src/interfaces/ILspService.d.ts +54 -0
- package/dist/src/interfaces/ILspService.js +6 -0
- package/dist/src/interfaces/ILspService.js.map +1 -0
- package/dist/src/interfaces/IMcpToolService.d.ts +47 -0
- package/dist/src/interfaces/IMcpToolService.js +6 -0
- package/dist/src/interfaces/IMcpToolService.js.map +1 -0
- package/dist/src/interfaces/IPromptRegistryService.d.ts +51 -0
- package/dist/src/interfaces/IPromptRegistryService.js +6 -0
- package/dist/src/interfaces/IPromptRegistryService.js.map +1 -0
- package/dist/src/interfaces/ISettingsService.d.ts +50 -0
- package/dist/src/interfaces/ISettingsService.js +6 -0
- package/dist/src/interfaces/ISettingsService.js.map +1 -0
- package/dist/src/interfaces/IShellExecutionService.d.ts +55 -0
- package/dist/src/interfaces/IShellExecutionService.js +6 -0
- package/dist/src/interfaces/IShellExecutionService.js.map +1 -0
- package/dist/src/interfaces/IShellToolHost.d.ts +176 -0
- package/dist/src/interfaces/IShellToolHost.js +6 -0
- package/dist/src/interfaces/IShellToolHost.js.map +1 -0
- package/dist/src/interfaces/ISkillService.d.ts +69 -0
- package/dist/src/interfaces/ISkillService.js +6 -0
- package/dist/src/interfaces/ISkillService.js.map +1 -0
- package/dist/src/interfaces/IStorageService.d.ts +42 -0
- package/dist/src/interfaces/IStorageService.js +6 -0
- package/dist/src/interfaces/IStorageService.js.map +1 -0
- package/dist/src/interfaces/ISubagentService.d.ts +112 -0
- package/dist/src/interfaces/ISubagentService.js +6 -0
- package/dist/src/interfaces/ISubagentService.js.map +1 -0
- package/dist/src/interfaces/ITaskToolHost.d.ts +185 -0
- package/dist/src/interfaces/ITaskToolHost.js +6 -0
- package/dist/src/interfaces/ITaskToolHost.js.map +1 -0
- package/dist/src/interfaces/ITodoService.d.ts +79 -0
- package/dist/src/interfaces/ITodoService.js +6 -0
- package/dist/src/interfaces/ITodoService.js.map +1 -0
- package/dist/src/interfaces/IToolHost.d.ts +91 -0
- package/dist/src/interfaces/IToolHost.js +6 -0
- package/dist/src/interfaces/IToolHost.js.map +1 -0
- package/dist/src/interfaces/IToolKeyStorage.d.ts +62 -0
- package/dist/src/interfaces/IToolKeyStorage.js +6 -0
- package/dist/src/interfaces/IToolKeyStorage.js.map +1 -0
- package/dist/src/interfaces/IToolMessageBus.d.ts +63 -0
- package/dist/src/interfaces/IToolMessageBus.js +6 -0
- package/dist/src/interfaces/IToolMessageBus.js.map +1 -0
- package/dist/src/interfaces/IToolRegistryHost.d.ts +43 -0
- package/dist/src/interfaces/IToolRegistryHost.js +6 -0
- package/dist/src/interfaces/IToolRegistryHost.js.map +1 -0
- package/dist/src/interfaces/IWebSearchService.d.ts +16 -0
- package/dist/src/interfaces/IWebSearchService.js +7 -0
- package/dist/src/interfaces/IWebSearchService.js.map +1 -0
- package/dist/src/interfaces/index.d.ts +33 -0
- package/dist/src/interfaces/index.js +6 -0
- package/dist/src/interfaces/index.js.map +1 -0
- package/dist/src/tools/activate-skill.d.ts +26 -0
- package/dist/src/tools/activate-skill.js +121 -0
- package/dist/src/tools/activate-skill.js.map +1 -0
- package/dist/src/tools/apply-patch.d.ts +65 -0
- package/dist/src/tools/apply-patch.js +528 -0
- package/dist/src/tools/apply-patch.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-config.d.ts +67 -0
- package/dist/src/tools/ast-edit/ast-config.js +72 -0
- package/dist/src/tools/ast-edit/ast-config.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-edit-invocation.d.ts +40 -0
- package/dist/src/tools/ast-edit/ast-edit-invocation.js +410 -0
- package/dist/src/tools/ast-edit/ast-edit-invocation.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-query-extractor.d.ts +21 -0
- package/dist/src/tools/ast-edit/ast-query-extractor.js +178 -0
- package/dist/src/tools/ast-edit/ast-query-extractor.js.map +1 -0
- package/dist/src/tools/ast-edit/ast-read-file-invocation.d.ts +26 -0
- package/dist/src/tools/ast-edit/ast-read-file-invocation.js +149 -0
- package/dist/src/tools/ast-edit/ast-read-file-invocation.js.map +1 -0
- package/dist/src/tools/ast-edit/constants.d.ts +30 -0
- package/dist/src/tools/ast-edit/constants.js +36 -0
- package/dist/src/tools/ast-edit/constants.js.map +1 -0
- package/dist/src/tools/ast-edit/context-collector.d.ts +25 -0
- package/dist/src/tools/ast-edit/context-collector.js +115 -0
- package/dist/src/tools/ast-edit/context-collector.js.map +1 -0
- package/dist/src/tools/ast-edit/context-optimizer.d.ts +29 -0
- package/dist/src/tools/ast-edit/context-optimizer.js +86 -0
- package/dist/src/tools/ast-edit/context-optimizer.js.map +1 -0
- package/dist/src/tools/ast-edit/cross-file-analyzer.d.ts +41 -0
- package/dist/src/tools/ast-edit/cross-file-analyzer.js +294 -0
- package/dist/src/tools/ast-edit/cross-file-analyzer.js.map +1 -0
- package/dist/src/tools/ast-edit/edit-calculator.d.ts +71 -0
- package/dist/src/tools/ast-edit/edit-calculator.js +249 -0
- package/dist/src/tools/ast-edit/edit-calculator.js.map +1 -0
- package/dist/src/tools/ast-edit/edit-helpers.d.ts +22 -0
- package/dist/src/tools/ast-edit/edit-helpers.js +36 -0
- package/dist/src/tools/ast-edit/edit-helpers.js.map +1 -0
- package/dist/src/tools/ast-edit/language-analysis.d.ts +19 -0
- package/dist/src/tools/ast-edit/language-analysis.js +123 -0
- package/dist/src/tools/ast-edit/language-analysis.js.map +1 -0
- package/dist/src/tools/ast-edit/local-context-analyzer.d.ts +84 -0
- package/dist/src/tools/ast-edit/local-context-analyzer.js +267 -0
- package/dist/src/tools/ast-edit/local-context-analyzer.js.map +1 -0
- package/dist/src/tools/ast-edit/repository-context-provider.d.ts +22 -0
- package/dist/src/tools/ast-edit/repository-context-provider.js +139 -0
- package/dist/src/tools/ast-edit/repository-context-provider.js.map +1 -0
- package/dist/src/tools/ast-edit/types.d.ts +155 -0
- package/dist/src/tools/ast-edit/types.js +7 -0
- package/dist/src/tools/ast-edit/types.js.map +1 -0
- package/dist/src/tools/ast-edit/workspace-context-provider.d.ts +22 -0
- package/dist/src/tools/ast-edit/workspace-context-provider.js +39 -0
- package/dist/src/tools/ast-edit/workspace-context-provider.js.map +1 -0
- package/dist/src/tools/ast-edit.d.ts +43 -0
- package/dist/src/tools/ast-edit.js +183 -0
- package/dist/src/tools/ast-edit.js.map +1 -0
- package/dist/src/tools/ast-grep.d.ts +22 -0
- package/dist/src/tools/ast-grep.js +291 -0
- package/dist/src/tools/ast-grep.js.map +1 -0
- package/dist/src/tools/check-async-tasks.d.ts +46 -0
- package/dist/src/tools/check-async-tasks.js +241 -0
- package/dist/src/tools/check-async-tasks.js.map +1 -0
- package/dist/src/tools/codesearch.d.ts +25 -0
- package/dist/src/tools/codesearch.js +153 -0
- package/dist/src/tools/codesearch.js.map +1 -0
- package/dist/src/tools/delete_line_range.d.ts +41 -0
- package/dist/src/tools/delete_line_range.js +238 -0
- package/dist/src/tools/delete_line_range.js.map +1 -0
- package/dist/src/tools/direct-web-fetch.d.ts +22 -0
- package/dist/src/tools/direct-web-fetch.js +215 -0
- package/dist/src/tools/direct-web-fetch.js.map +1 -0
- package/dist/src/tools/edit-utils.d.ts +53 -0
- package/dist/src/tools/edit-utils.js +250 -0
- package/dist/src/tools/edit-utils.js.map +1 -0
- package/dist/src/tools/edit.d.ts +70 -0
- package/dist/src/tools/edit.js +816 -0
- package/dist/src/tools/edit.js.map +1 -0
- package/dist/src/tools/exa-web-search.d.ts +27 -0
- package/dist/src/tools/exa-web-search.js +153 -0
- package/dist/src/tools/exa-web-search.js.map +1 -0
- package/dist/src/tools/glob.d.ts +55 -0
- package/dist/src/tools/glob.js +284 -0
- package/dist/src/tools/glob.js.map +1 -0
- package/dist/src/tools/google-web-fetch.d.ts +34 -0
- package/dist/src/tools/google-web-fetch.js +417 -0
- package/dist/src/tools/google-web-fetch.js.map +1 -0
- package/dist/src/tools/google-web-search-invocation.d.ts +53 -0
- package/dist/src/tools/google-web-search-invocation.js +180 -0
- package/dist/src/tools/google-web-search-invocation.js.map +1 -0
- package/dist/src/tools/google-web-search.d.ts +16 -0
- package/dist/src/tools/google-web-search.js +34 -0
- package/dist/src/tools/google-web-search.js.map +1 -0
- package/dist/src/tools/grep.d.ts +56 -0
- package/dist/src/tools/grep.js +883 -0
- package/dist/src/tools/grep.js.map +1 -0
- package/dist/src/tools/insert_at_line.d.ts +41 -0
- package/dist/src/tools/insert_at_line.js +287 -0
- package/dist/src/tools/insert_at_line.js.map +1 -0
- package/dist/src/tools/list-subagents.d.ts +31 -0
- package/dist/src/tools/list-subagents.js +122 -0
- package/dist/src/tools/list-subagents.js.map +1 -0
- package/dist/src/tools/ls.d.ts +71 -0
- package/dist/src/tools/ls.js +238 -0
- package/dist/src/tools/ls.js.map +1 -0
- package/dist/src/tools/memoryTool.d.ts +66 -0
- package/dist/src/tools/memoryTool.js +435 -0
- package/dist/src/tools/memoryTool.js.map +1 -0
- package/dist/src/tools/modifiable-tool.d.ts +37 -0
- package/dist/src/tools/modifiable-tool.js +120 -0
- package/dist/src/tools/modifiable-tool.js.map +1 -0
- package/dist/src/tools/read-file.d.ts +49 -0
- package/dist/src/tools/read-file.js +279 -0
- package/dist/src/tools/read-file.js.map +1 -0
- package/dist/src/tools/read-many-files.d.ts +60 -0
- package/dist/src/tools/read-many-files.js +529 -0
- package/dist/src/tools/read-many-files.js.map +1 -0
- package/dist/src/tools/read_line_range.d.ts +47 -0
- package/dist/src/tools/read_line_range.js +248 -0
- package/dist/src/tools/read_line_range.js.map +1 -0
- package/dist/src/tools/ripGrep.d.ts +41 -0
- package/dist/src/tools/ripGrep.js +395 -0
- package/dist/src/tools/ripGrep.js.map +1 -0
- package/dist/src/tools/shell.d.ts +60 -0
- package/dist/src/tools/shell.js +735 -0
- package/dist/src/tools/shell.js.map +1 -0
- package/dist/src/tools/structural-analysis.d.ts +27 -0
- package/dist/src/tools/structural-analysis.js +1089 -0
- package/dist/src/tools/structural-analysis.js.map +1 -0
- package/dist/src/tools/stubs.d.ts +10 -0
- package/dist/src/tools/stubs.js +6 -0
- package/dist/src/tools/stubs.js.map +1 -0
- package/dist/src/tools/task.d.ts +41 -0
- package/dist/src/tools/task.js +195 -0
- package/dist/src/tools/task.js.map +1 -0
- package/dist/src/tools/todo-events.d.ts +22 -0
- package/dist/src/tools/todo-events.js +24 -0
- package/dist/src/tools/todo-events.js.map +1 -0
- package/dist/src/tools/todo-pause.d.ts +29 -0
- package/dist/src/tools/todo-pause.js +172 -0
- package/dist/src/tools/todo-pause.js.map +1 -0
- package/dist/src/tools/todo-read.d.ts +18 -0
- package/dist/src/tools/todo-read.js +98 -0
- package/dist/src/tools/todo-read.js.map +1 -0
- package/dist/src/tools/todo-store.d.ts +35 -0
- package/dist/src/tools/todo-store.js +124 -0
- package/dist/src/tools/todo-store.js.map +1 -0
- package/dist/src/tools/todo-write.d.ts +29 -0
- package/dist/src/tools/todo-write.js +366 -0
- package/dist/src/tools/todo-write.js.map +1 -0
- package/dist/src/tools/tool-registry.d.ts +156 -0
- package/dist/src/tools/tool-registry.js +623 -0
- package/dist/src/tools/tool-registry.js.map +1 -0
- package/dist/src/tools/tools.d.ts +403 -0
- package/dist/src/tools/tools.js +519 -0
- package/dist/src/tools/tools.js.map +1 -0
- package/dist/src/tools/write-file.d.ts +45 -0
- package/dist/src/tools/write-file.js +320 -0
- package/dist/src/tools/write-file.js.map +1 -0
- package/dist/src/types/index.d.ts +17 -0
- package/dist/src/types/index.js +9 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/provider-content-types.d.ts +56 -0
- package/dist/src/types/provider-content-types.js +6 -0
- package/dist/src/types/provider-content-types.js.map +1 -0
- package/dist/src/types/todo-schemas.d.ts +263 -0
- package/dist/src/types/todo-schemas.js +32 -0
- package/dist/src/types/todo-schemas.js.map +1 -0
- package/dist/src/types/tool-confirmation-types.d.ts +34 -0
- package/dist/src/types/tool-confirmation-types.js +29 -0
- package/dist/src/types/tool-confirmation-types.js.map +1 -0
- package/dist/src/types/tool-context.d.ts +31 -0
- package/dist/src/types/tool-context.js +6 -0
- package/dist/src/types/tool-context.js.map +1 -0
- package/dist/src/types/tool-error.d.ts +69 -0
- package/dist/src/types/tool-error.js +94 -0
- package/dist/src/types/tool-error.js.map +1 -0
- package/dist/src/types/tool-names.d.ts +57 -0
- package/dist/src/types/tool-names.js +64 -0
- package/dist/src/types/tool-names.js.map +1 -0
- package/dist/src/utils/EmojiFilter.d.ts +134 -0
- package/dist/src/utils/EmojiFilter.js +370 -0
- package/dist/src/utils/EmojiFilter.js.map +1 -0
- package/dist/src/utils/ast-grep-utils.d.ts +42 -0
- package/dist/src/utils/ast-grep-utils.js +140 -0
- package/dist/src/utils/ast-grep-utils.js.map +1 -0
- package/dist/src/utils/debugLogger.d.ts +11 -0
- package/dist/src/utils/debugLogger.js +16 -0
- package/dist/src/utils/debugLogger.js.map +1 -0
- package/dist/src/utils/diffOptions.d.ts +14 -0
- package/dist/src/utils/diffOptions.js +46 -0
- package/dist/src/utils/diffOptions.js.map +1 -0
- package/dist/src/utils/editor.d.ts +39 -0
- package/dist/src/utils/editor.js +212 -0
- package/dist/src/utils/editor.js.map +1 -0
- package/dist/src/utils/ensure-dirs.d.ts +10 -0
- package/dist/src/utils/ensure-dirs.js +16 -0
- package/dist/src/utils/ensure-dirs.js.map +1 -0
- package/dist/src/utils/errors.d.ts +59 -0
- package/dist/src/utils/errors.js +178 -0
- package/dist/src/utils/errors.js.map +1 -0
- package/dist/src/utils/fetch.d.ts +11 -0
- package/dist/src/utils/fetch.js +74 -0
- package/dist/src/utils/fetch.js.map +1 -0
- package/dist/src/utils/fileUtils.d.ts +32 -0
- package/dist/src/utils/fileUtils.js +363 -0
- package/dist/src/utils/fileUtils.js.map +1 -0
- package/dist/src/utils/fuzzy-replacer.d.ts +61 -0
- package/dist/src/utils/fuzzy-replacer.js +492 -0
- package/dist/src/utils/fuzzy-replacer.js.map +1 -0
- package/dist/src/utils/gitLineChanges.d.ts +12 -0
- package/dist/src/utils/gitLineChanges.js +171 -0
- package/dist/src/utils/gitLineChanges.js.map +1 -0
- package/dist/src/utils/gitUtils.d.ts +6 -0
- package/dist/src/utils/gitUtils.js +31 -0
- package/dist/src/utils/gitUtils.js.map +1 -0
- package/dist/src/utils/lsp-diagnostics-helper.d.ts +27 -0
- package/dist/src/utils/lsp-diagnostics-helper.js +62 -0
- package/dist/src/utils/lsp-diagnostics-helper.js.map +1 -0
- package/dist/src/utils/mediaUtils.d.ts +38 -0
- package/dist/src/utils/mediaUtils.js +22 -0
- package/dist/src/utils/mediaUtils.js.map +1 -0
- package/dist/src/utils/pathValidation.d.ts +7 -0
- package/dist/src/utils/pathValidation.js +39 -0
- package/dist/src/utils/pathValidation.js.map +1 -0
- package/dist/src/utils/paths.d.ts +7 -0
- package/dist/src/utils/paths.js +158 -0
- package/dist/src/utils/paths.js.map +1 -0
- package/dist/src/utils/resolveTextSearchTarget.d.ts +17 -0
- package/dist/src/utils/resolveTextSearchTarget.js +45 -0
- package/dist/src/utils/resolveTextSearchTarget.js.map +1 -0
- package/dist/src/utils/retry.d.ts +17 -0
- package/dist/src/utils/retry.js +185 -0
- package/dist/src/utils/retry.js.map +1 -0
- package/dist/src/utils/ripgrepPathResolver.d.ts +23 -0
- package/dist/src/utils/ripgrepPathResolver.js +179 -0
- package/dist/src/utils/ripgrepPathResolver.js.map +1 -0
- package/dist/src/utils/safeJsonStringify.d.ts +9 -0
- package/dist/src/utils/safeJsonStringify.js +21 -0
- package/dist/src/utils/safeJsonStringify.js.map +1 -0
- package/dist/src/utils/schemaValidator.d.ts +13 -0
- package/dist/src/utils/schemaValidator.js +275 -0
- package/dist/src/utils/schemaValidator.js.map +1 -0
- package/dist/src/utils/terminalSerializer.d.ts +33 -0
- package/dist/src/utils/terminalSerializer.js +6 -0
- package/dist/src/utils/terminalSerializer.js.map +1 -0
- package/dist/src/utils/todoContextTracker.d.ts +12 -0
- package/dist/src/utils/todoContextTracker.js +28 -0
- package/dist/src/utils/todoContextTracker.js.map +1 -0
- package/dist/src/utils/todoFormatter.d.ts +24 -0
- package/dist/src/utils/todoFormatter.js +157 -0
- package/dist/src/utils/todoFormatter.js.map +1 -0
- package/dist/src/utils/todoReminderService.d.ts +22 -0
- package/dist/src/utils/todoReminderService.js +43 -0
- package/dist/src/utils/todoReminderService.js.map +1 -0
- package/dist/src/utils/tool-key-storage-facade.d.ts +32 -0
- package/dist/src/utils/tool-key-storage-facade.js +56 -0
- package/dist/src/utils/tool-key-storage-facade.js.map +1 -0
- package/dist/src/utils/tool-key-storage-types.d.ts +56 -0
- package/dist/src/utils/tool-key-storage-types.js +56 -0
- package/dist/src/utils/tool-key-storage-types.js.map +1 -0
- package/dist/src/utils/toolOutputLimiter.d.ts +29 -0
- package/dist/src/utils/toolOutputLimiter.js +107 -0
- package/dist/src/utils/toolOutputLimiter.js.map +1 -0
- package/dist/src/utils/unicodeUtils.d.ts +55 -0
- package/dist/src/utils/unicodeUtils.js +129 -0
- package/dist/src/utils/unicodeUtils.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-hop AST-based code analysis tool.
|
|
3
|
+
* Analyzes code relationships: call graphs, type hierarchies, symbol references,
|
|
4
|
+
* module dependencies, and exports using @ast-grep/napi.
|
|
5
|
+
*
|
|
6
|
+
* This is name-based (not type-resolved) analysis. See overview.md for limitations.
|
|
7
|
+
*
|
|
8
|
+
* @plan PLAN-20260211-ASTGREP.P07
|
|
9
|
+
*/
|
|
10
|
+
/* eslint-disable complexity, sonarjs/cognitive-complexity, max-lines -- Phase 5: legacy core boundary retained while larger decomposition continues. */
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import { promises as fs } from 'node:fs';
|
|
13
|
+
import FastGlob from 'fast-glob';
|
|
14
|
+
import { BaseDeclarativeTool, BaseToolInvocation, Kind, } from './tools.js';
|
|
15
|
+
import { makeRelative } from '../utils/paths.js';
|
|
16
|
+
import { parse, getAstLanguage, LANGUAGE_MAP, } from '../utils/ast-grep-utils.js';
|
|
17
|
+
const VALID_MODES = [
|
|
18
|
+
'callers',
|
|
19
|
+
'callees',
|
|
20
|
+
'definitions',
|
|
21
|
+
'hierarchy',
|
|
22
|
+
'references',
|
|
23
|
+
'dependencies',
|
|
24
|
+
'exports',
|
|
25
|
+
];
|
|
26
|
+
const DEFAULT_DEPTH = 1;
|
|
27
|
+
const MAX_DEPTH = 5;
|
|
28
|
+
const DEFAULT_MAX_NODES = 50;
|
|
29
|
+
class StructuralAnalysisInvocation extends BaseToolInvocation {
|
|
30
|
+
host;
|
|
31
|
+
constructor(host, params, messageBus) {
|
|
32
|
+
super(params, messageBus);
|
|
33
|
+
this.host = host;
|
|
34
|
+
}
|
|
35
|
+
getDescription() {
|
|
36
|
+
const { mode, symbol } = this.params;
|
|
37
|
+
if (symbol)
|
|
38
|
+
return `${mode}: ${symbol}`;
|
|
39
|
+
return `${mode} analysis`;
|
|
40
|
+
}
|
|
41
|
+
makeError(message) {
|
|
42
|
+
return { llmContent: message, returnDisplay: message };
|
|
43
|
+
}
|
|
44
|
+
formatResult(analysisResult) {
|
|
45
|
+
const llmContent = JSON.stringify(analysisResult, null, 2);
|
|
46
|
+
const mode = analysisResult.mode;
|
|
47
|
+
const symbol = analysisResult.symbol ? ` for ${analysisResult.symbol}` : '';
|
|
48
|
+
const displayMessage = `${mode} analysis${symbol} complete${analysisResult.truncated ? ' (truncated)' : ''}`;
|
|
49
|
+
return {
|
|
50
|
+
llmContent,
|
|
51
|
+
returnDisplay: displayMessage,
|
|
52
|
+
metadata: analysisResult,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
validateAndResolveParams() {
|
|
56
|
+
const { mode, language, symbol, depth, maxNodes, target, reverse } = this.params;
|
|
57
|
+
if (!VALID_MODES.includes(mode)) {
|
|
58
|
+
return this.makeError(`Error: Invalid mode "${mode}". Valid modes: ${VALID_MODES.join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
if (!language) {
|
|
61
|
+
return this.makeError('Error: `language` parameter is required.');
|
|
62
|
+
}
|
|
63
|
+
const resolvedLang = getAstLanguage(language);
|
|
64
|
+
if (!resolvedLang) {
|
|
65
|
+
return this.makeError(`Error: Unrecognized language "${language}". Supported: ${Object.keys(LANGUAGE_MAP).join(', ')}`);
|
|
66
|
+
}
|
|
67
|
+
const targetDir = this.host.getTargetDir();
|
|
68
|
+
let searchPath = this.params.path ?? target ?? targetDir;
|
|
69
|
+
if (!path.isAbsolute(searchPath)) {
|
|
70
|
+
searchPath = path.resolve(targetDir, searchPath);
|
|
71
|
+
}
|
|
72
|
+
const normalizedTarget = targetDir.endsWith(path.sep)
|
|
73
|
+
? targetDir
|
|
74
|
+
: targetDir + path.sep;
|
|
75
|
+
if (searchPath !== targetDir && !searchPath.startsWith(normalizedTarget)) {
|
|
76
|
+
return this.makeError('Error: Path resolves outside the workspace root.');
|
|
77
|
+
}
|
|
78
|
+
const symbolModes = [
|
|
79
|
+
'callers',
|
|
80
|
+
'callees',
|
|
81
|
+
'definitions',
|
|
82
|
+
'hierarchy',
|
|
83
|
+
'references',
|
|
84
|
+
];
|
|
85
|
+
if (symbolModes.includes(mode) && !symbol) {
|
|
86
|
+
return this.makeError(`Error: \`symbol\` parameter is required for "${mode}" mode.`);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
mode,
|
|
90
|
+
resolvedLang,
|
|
91
|
+
searchPath,
|
|
92
|
+
targetDir,
|
|
93
|
+
symbol,
|
|
94
|
+
depth: Math.min(depth ?? DEFAULT_DEPTH, MAX_DEPTH),
|
|
95
|
+
maxNodes: maxNodes ?? DEFAULT_MAX_NODES,
|
|
96
|
+
reverse: reverse === true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async dispatchMode(mode, resolvedLang, searchPath, targetDir, symbol, effectiveDepth, effectiveMaxNodes, reverse, signal) {
|
|
100
|
+
switch (mode) {
|
|
101
|
+
case 'definitions':
|
|
102
|
+
return this.executeDefinitions(symbol, resolvedLang, searchPath, targetDir, signal);
|
|
103
|
+
case 'hierarchy':
|
|
104
|
+
return this.executeHierarchy(symbol, resolvedLang, searchPath, targetDir, signal);
|
|
105
|
+
case 'callers':
|
|
106
|
+
return this.executeCallers(symbol, resolvedLang, searchPath, targetDir, effectiveDepth, effectiveMaxNodes, signal);
|
|
107
|
+
case 'callees':
|
|
108
|
+
return this.executeCallees(symbol, resolvedLang, searchPath, targetDir, effectiveDepth, effectiveMaxNodes, signal);
|
|
109
|
+
case 'references':
|
|
110
|
+
return this.executeReferences(symbol, resolvedLang, searchPath, targetDir, signal);
|
|
111
|
+
case 'dependencies':
|
|
112
|
+
return this.executeDependencies(searchPath, resolvedLang, targetDir, reverse, signal);
|
|
113
|
+
case 'exports':
|
|
114
|
+
return this.executeExports(searchPath, resolvedLang, targetDir, signal);
|
|
115
|
+
default:
|
|
116
|
+
throw new Error(`Mode "${mode}" is not implemented.`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async execute(signal) {
|
|
120
|
+
const params = this.validateAndResolveParams();
|
|
121
|
+
if ('llmContent' in params)
|
|
122
|
+
return params;
|
|
123
|
+
const { mode, resolvedLang, searchPath, targetDir, symbol, depth: effectiveDepth, maxNodes: effectiveMaxNodes, reverse, } = params;
|
|
124
|
+
try {
|
|
125
|
+
const analysisResult = await this.dispatchMode(mode, resolvedLang, searchPath, targetDir, symbol, effectiveDepth, effectiveMaxNodes, reverse, signal);
|
|
126
|
+
return this.formatResult(analysisResult);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (err instanceof Error && err.message.endsWith('is not implemented.')) {
|
|
130
|
+
return this.makeError(`Error: ${err.message}`);
|
|
131
|
+
}
|
|
132
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
133
|
+
return this.makeError(`Error in ${mode}: ${msg}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async getFiles(searchPath, lang) {
|
|
137
|
+
const stat = await fs.stat(searchPath).catch(() => null);
|
|
138
|
+
if (stat !== null && stat.isFile() === true) {
|
|
139
|
+
return [searchPath];
|
|
140
|
+
}
|
|
141
|
+
const extensions = this.getExtensionsForLanguage(lang);
|
|
142
|
+
return FastGlob(extensions.map((ext) => `**/*.${ext}`), {
|
|
143
|
+
cwd: searchPath,
|
|
144
|
+
absolute: true,
|
|
145
|
+
dot: false,
|
|
146
|
+
ignore: ['**/node_modules/**', '**/.git/**'],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
getExtensionsForLanguage(lang) {
|
|
150
|
+
const exts = [];
|
|
151
|
+
for (const [ext, mapped] of Object.entries(LANGUAGE_MAP)) {
|
|
152
|
+
if (mapped === lang)
|
|
153
|
+
exts.push(ext);
|
|
154
|
+
}
|
|
155
|
+
return exts.length > 0 ? exts : ['*'];
|
|
156
|
+
}
|
|
157
|
+
escapeRegex(s) {
|
|
158
|
+
// eslint-disable-next-line sonarjs/regular-expr -- Static regex reviewed for lint hardening; behavior preserved.
|
|
159
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* AST node kinds that represent function-like containers.
|
|
163
|
+
* Used for callers mode to find the scope a call lives in.
|
|
164
|
+
*/
|
|
165
|
+
static FUNCTION_CONTAINER_KINDS = new Set([
|
|
166
|
+
'method_definition',
|
|
167
|
+
'function_declaration',
|
|
168
|
+
'arrow_function',
|
|
169
|
+
]);
|
|
170
|
+
/**
|
|
171
|
+
* Extract a name from a function-like container node.
|
|
172
|
+
* - method_definition: first property_identifier child
|
|
173
|
+
* - function_declaration: first identifier child
|
|
174
|
+
* - arrow_function: name from parent variable_declarator's identifier
|
|
175
|
+
*/
|
|
176
|
+
getContainerName(node) {
|
|
177
|
+
const kind = String(node.kind());
|
|
178
|
+
if (kind === 'method_definition') {
|
|
179
|
+
const nameNode = node
|
|
180
|
+
.children()
|
|
181
|
+
.find((c) => String(c.kind()) === 'property_identifier');
|
|
182
|
+
return nameNode?.text() ?? null;
|
|
183
|
+
}
|
|
184
|
+
if (kind === 'function_declaration') {
|
|
185
|
+
const nameNode = node
|
|
186
|
+
.children()
|
|
187
|
+
.find((c) => String(c.kind()) === 'identifier');
|
|
188
|
+
return nameNode?.text() ?? null;
|
|
189
|
+
}
|
|
190
|
+
if (kind === 'arrow_function') {
|
|
191
|
+
// Walk up to variable_declarator → get its identifier
|
|
192
|
+
const parent = node.parent();
|
|
193
|
+
if (parent && String(parent.kind()) === 'variable_declarator') {
|
|
194
|
+
const nameNode = parent
|
|
195
|
+
.children()
|
|
196
|
+
.find((c) => String(c.kind()) === 'identifier');
|
|
197
|
+
return nameNode?.text() ?? null;
|
|
198
|
+
}
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
async parseFile(filePath, lang) {
|
|
204
|
+
try {
|
|
205
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
206
|
+
const result = parse(lang, content);
|
|
207
|
+
return { root: result.root(), content };
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Extracts the function/method name from a call_expression node.
|
|
215
|
+
* Handles: `foo()` → "foo", `obj.bar()` → "bar", `a.b.c()` → "c"
|
|
216
|
+
*/
|
|
217
|
+
extractCalleeName(callNode) {
|
|
218
|
+
const children = callNode.children();
|
|
219
|
+
if (children.length === 0)
|
|
220
|
+
return null;
|
|
221
|
+
const callee = children[0]; // first child of call_expression is the callee
|
|
222
|
+
const kind = String(callee.kind());
|
|
223
|
+
if (kind === 'identifier') {
|
|
224
|
+
return callee.text();
|
|
225
|
+
}
|
|
226
|
+
if (kind === 'member_expression') {
|
|
227
|
+
// Last property_identifier child is the method name
|
|
228
|
+
const props = callee
|
|
229
|
+
.children()
|
|
230
|
+
.filter((c) => String(c.kind()) === 'property_identifier');
|
|
231
|
+
if (props.length > 0) {
|
|
232
|
+
return props[props.length - 1].text();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
// ===== DEFINITIONS =====
|
|
238
|
+
searchDefinitionPatterns(parsed, symbol, relPath, definitions) {
|
|
239
|
+
const patterns = [
|
|
240
|
+
{ pat: `${symbol}($$$PARAMS) { $$$BODY }`, kind: 'method' },
|
|
241
|
+
{ pat: `function ${symbol}($$$PARAMS) { $$$BODY }`, kind: 'function' },
|
|
242
|
+
{ pat: `class ${symbol} { $$$BODY }`, kind: 'class' },
|
|
243
|
+
{ pat: `class ${symbol} extends $PARENT { $$$BODY }`, kind: 'class' },
|
|
244
|
+
];
|
|
245
|
+
for (const { pat, kind } of patterns) {
|
|
246
|
+
try {
|
|
247
|
+
const matches = parsed.root.findAll(pat);
|
|
248
|
+
for (const m of matches) {
|
|
249
|
+
const range = m.range();
|
|
250
|
+
definitions.push({
|
|
251
|
+
file: relPath,
|
|
252
|
+
line: range.start.line + 1,
|
|
253
|
+
kind,
|
|
254
|
+
text: m.text().substring(0, 200),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// Pattern may not be valid for all languages
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
searchDeclarationRules(parsed, symbol, relPath, definitions) {
|
|
264
|
+
try {
|
|
265
|
+
const ruleMatches = parsed.root.findAll({
|
|
266
|
+
rule: {
|
|
267
|
+
any: [
|
|
268
|
+
{
|
|
269
|
+
kind: 'class_declaration',
|
|
270
|
+
has: {
|
|
271
|
+
kind: 'type_identifier',
|
|
272
|
+
regex: `^${this.escapeRegex(symbol)}$`,
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
kind: 'interface_declaration',
|
|
277
|
+
has: {
|
|
278
|
+
kind: 'type_identifier',
|
|
279
|
+
regex: `^${this.escapeRegex(symbol)}$`,
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
kind: 'type_alias_declaration',
|
|
284
|
+
has: {
|
|
285
|
+
kind: 'type_identifier',
|
|
286
|
+
regex: `^${this.escapeRegex(symbol)}$`,
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
for (const m of ruleMatches) {
|
|
293
|
+
const range = m.range();
|
|
294
|
+
const exists = definitions.some((d) => d.file === relPath && d.line === range.start.line + 1);
|
|
295
|
+
if (!exists) {
|
|
296
|
+
definitions.push({
|
|
297
|
+
file: relPath,
|
|
298
|
+
line: range.start.line + 1,
|
|
299
|
+
kind: String(m.kind()),
|
|
300
|
+
text: m.text().substring(0, 200),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
// Rule may not apply
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
async executeDefinitions(symbol, lang, searchPath, workspaceRoot, signal) {
|
|
310
|
+
const files = await this.getFiles(searchPath, lang);
|
|
311
|
+
const definitions = [];
|
|
312
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
313
|
+
for (const file of files) {
|
|
314
|
+
if (signal.aborted)
|
|
315
|
+
break;
|
|
316
|
+
const parsed = await this.parseFile(file, lang);
|
|
317
|
+
if (!parsed)
|
|
318
|
+
continue;
|
|
319
|
+
const relPath = makeRelative(file, workspaceRoot);
|
|
320
|
+
this.searchDefinitionPatterns(parsed, symbol, relPath, definitions);
|
|
321
|
+
this.searchDeclarationRules(parsed, symbol, relPath, definitions);
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
mode: 'definitions',
|
|
325
|
+
symbol,
|
|
326
|
+
truncated: false,
|
|
327
|
+
results: definitions,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
// ===== HIERARCHY =====
|
|
331
|
+
findSymbolParents(parsed, symbol, extendsParent, implementsInterfaces) {
|
|
332
|
+
try {
|
|
333
|
+
const extendsMatches = parsed.root.findAll(`class ${symbol} extends $PARENT { $$$BODY }`);
|
|
334
|
+
for (const m of extendsMatches) {
|
|
335
|
+
const parent = m.getMatch('PARENT');
|
|
336
|
+
if (parent)
|
|
337
|
+
extendsParent.push(parent.text());
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
/* skip */
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const implMatches = parsed.root.findAll(`class ${symbol} implements $IFACE { $$$BODY }`);
|
|
345
|
+
for (const m of implMatches) {
|
|
346
|
+
const iface = m.getMatch('IFACE');
|
|
347
|
+
if (iface)
|
|
348
|
+
implementsInterfaces.push(iface.text());
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
/* skip */
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
findSymbolChildren(parsed, symbol, relPath, extendedBy, implementedBy) {
|
|
356
|
+
try {
|
|
357
|
+
const childMatches = parsed.root.findAll(`class $NAME extends ${symbol} { $$$BODY }`);
|
|
358
|
+
for (const m of childMatches) {
|
|
359
|
+
const name = m.getMatch('NAME');
|
|
360
|
+
if (name) {
|
|
361
|
+
extendedBy.push({
|
|
362
|
+
name: name.text(),
|
|
363
|
+
file: relPath,
|
|
364
|
+
line: m.range().start.line + 1,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
/* skip */
|
|
371
|
+
}
|
|
372
|
+
try {
|
|
373
|
+
const implByMatches = parsed.root.findAll(`class $NAME implements ${symbol} { $$$BODY }`);
|
|
374
|
+
for (const m of implByMatches) {
|
|
375
|
+
const name = m.getMatch('NAME');
|
|
376
|
+
if (name) {
|
|
377
|
+
implementedBy.push({
|
|
378
|
+
name: name.text(),
|
|
379
|
+
file: relPath,
|
|
380
|
+
line: m.range().start.line + 1,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
/* skip */
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async executeHierarchy(symbol, lang, searchPath, workspaceRoot, signal) {
|
|
390
|
+
const files = await this.getFiles(searchPath, lang);
|
|
391
|
+
const extendsParent = [];
|
|
392
|
+
const implementsInterfaces = [];
|
|
393
|
+
const extendedBy = [];
|
|
394
|
+
const implementedBy = [];
|
|
395
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
396
|
+
for (const file of files) {
|
|
397
|
+
if (signal.aborted)
|
|
398
|
+
break;
|
|
399
|
+
const parsed = await this.parseFile(file, lang);
|
|
400
|
+
if (!parsed)
|
|
401
|
+
continue;
|
|
402
|
+
const relPath = makeRelative(file, workspaceRoot);
|
|
403
|
+
this.findSymbolParents(parsed, symbol, extendsParent, implementsInterfaces);
|
|
404
|
+
this.findSymbolChildren(parsed, symbol, relPath, extendedBy, implementedBy);
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
mode: 'hierarchy',
|
|
408
|
+
symbol,
|
|
409
|
+
truncated: false,
|
|
410
|
+
results: {
|
|
411
|
+
extends: extendsParent,
|
|
412
|
+
implements: implementsInterfaces,
|
|
413
|
+
extendedBy,
|
|
414
|
+
implementedBy,
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
// ===== CALLERS =====
|
|
419
|
+
// @plan PLAN-20260211-ASTGREP.P09
|
|
420
|
+
findFunctionContainer(node) {
|
|
421
|
+
let container = node.parent();
|
|
422
|
+
while (container &&
|
|
423
|
+
!StructuralAnalysisInvocation.FUNCTION_CONTAINER_KINDS.has(String(container.kind()))) {
|
|
424
|
+
container = container.parent();
|
|
425
|
+
}
|
|
426
|
+
return container;
|
|
427
|
+
}
|
|
428
|
+
getViaContext(callNode) {
|
|
429
|
+
let node = callNode;
|
|
430
|
+
let parentNode = node.parent();
|
|
431
|
+
while (parentNode) {
|
|
432
|
+
const parentKind = String(parentNode.kind());
|
|
433
|
+
if (parentKind.includes('statement') ||
|
|
434
|
+
StructuralAnalysisInvocation.FUNCTION_CONTAINER_KINDS.has(parentKind)) {
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
node = parentNode;
|
|
438
|
+
parentNode = node.parent();
|
|
439
|
+
}
|
|
440
|
+
return node.text().trim().substring(0, 200);
|
|
441
|
+
}
|
|
442
|
+
buildCallerEntry(callNode, sym, relPath, visited, via) {
|
|
443
|
+
const container = this.findFunctionContainer(callNode);
|
|
444
|
+
if (container === null) {
|
|
445
|
+
return undefined;
|
|
446
|
+
}
|
|
447
|
+
const methodName = this.getContainerName(container);
|
|
448
|
+
if (methodName === null || methodName === '' || methodName === sym) {
|
|
449
|
+
return undefined;
|
|
450
|
+
}
|
|
451
|
+
const key = `${methodName}@${relPath}`;
|
|
452
|
+
if (visited.has(key)) {
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
visited.add(key);
|
|
456
|
+
return {
|
|
457
|
+
method: methodName,
|
|
458
|
+
file: relPath,
|
|
459
|
+
line: container.range().start.line + 1,
|
|
460
|
+
via: via ?? this.getViaContext(callNode),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
findMemberCallCallers(parsed, sym, relPath, visited, ctx) {
|
|
464
|
+
const results = [];
|
|
465
|
+
try {
|
|
466
|
+
const memberCalls = parsed.root.findAll({
|
|
467
|
+
rule: {
|
|
468
|
+
kind: 'member_expression',
|
|
469
|
+
has: {
|
|
470
|
+
kind: 'property_identifier',
|
|
471
|
+
regex: `^${this.escapeRegex(sym)}$`,
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
for (const callNode of memberCalls) {
|
|
476
|
+
if (ctx.nodesVisited >= ctx.maxNodes) {
|
|
477
|
+
ctx.truncated = true;
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
const entry = this.buildCallerEntry(callNode, sym, relPath, visited);
|
|
481
|
+
if (entry !== undefined) {
|
|
482
|
+
ctx.nodesVisited++;
|
|
483
|
+
results.push(entry);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
/* skip */
|
|
489
|
+
}
|
|
490
|
+
return results;
|
|
491
|
+
}
|
|
492
|
+
findDirectCallCallers(parsed, sym, relPath, visited, ctx) {
|
|
493
|
+
const results = [];
|
|
494
|
+
try {
|
|
495
|
+
const directCallNodes = parsed.root.findAll(`${sym}($$$ARGS)`);
|
|
496
|
+
for (const callNode of directCallNodes) {
|
|
497
|
+
const entry = this.buildCallerEntry(callNode, sym, relPath, visited, `${sym}(...)`);
|
|
498
|
+
if (entry !== undefined) {
|
|
499
|
+
ctx.nodesVisited++;
|
|
500
|
+
results.push(entry);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
/* skip */
|
|
506
|
+
}
|
|
507
|
+
return results;
|
|
508
|
+
}
|
|
509
|
+
async findCallersOfFile(file, lang, sym, workspaceRoot, visited, ctx) {
|
|
510
|
+
if (ctx.signal.aborted || ctx.nodesVisited >= ctx.maxNodes)
|
|
511
|
+
return [];
|
|
512
|
+
const parsed = await this.parseFile(file, lang);
|
|
513
|
+
if (!parsed)
|
|
514
|
+
return [];
|
|
515
|
+
const relPath = makeRelative(file, workspaceRoot);
|
|
516
|
+
const memberResults = this.findMemberCallCallers(parsed, sym, relPath, visited, ctx);
|
|
517
|
+
const directResults = this.findDirectCallCallers(parsed, sym, relPath, visited, ctx);
|
|
518
|
+
return [...memberResults, ...directResults];
|
|
519
|
+
}
|
|
520
|
+
async executeCallers(symbol, lang, searchPath, workspaceRoot, depth, maxNodes, signal) {
|
|
521
|
+
const files = await this.getFiles(searchPath, lang);
|
|
522
|
+
const visited = new Set();
|
|
523
|
+
let nodesVisited = 0;
|
|
524
|
+
let truncated = false;
|
|
525
|
+
const findCallersOf = async (sym, currentDepth) => {
|
|
526
|
+
if (currentDepth <= 0 || nodesVisited >= maxNodes || signal.aborted) {
|
|
527
|
+
if (nodesVisited >= maxNodes)
|
|
528
|
+
truncated = true;
|
|
529
|
+
return [];
|
|
530
|
+
}
|
|
531
|
+
const callers = [];
|
|
532
|
+
const ctx = { nodesVisited, maxNodes, truncated, signal };
|
|
533
|
+
const syncTraversalState = () => {
|
|
534
|
+
nodesVisited = ctx.nodesVisited;
|
|
535
|
+
truncated = ctx.truncated;
|
|
536
|
+
};
|
|
537
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
538
|
+
for (const file of files) {
|
|
539
|
+
const fileResults = await this.findCallersOfFile(file, lang, sym, workspaceRoot, visited, ctx);
|
|
540
|
+
for (const r of fileResults) {
|
|
541
|
+
const entry = {
|
|
542
|
+
method: r.method,
|
|
543
|
+
file: r.file,
|
|
544
|
+
line: r.line,
|
|
545
|
+
via: r.via,
|
|
546
|
+
};
|
|
547
|
+
if (currentDepth > 1 && ctx.nodesVisited < maxNodes) {
|
|
548
|
+
syncTraversalState();
|
|
549
|
+
entry.callers = await findCallersOf(r.method, currentDepth - 1);
|
|
550
|
+
ctx.nodesVisited = nodesVisited;
|
|
551
|
+
ctx.truncated = truncated;
|
|
552
|
+
}
|
|
553
|
+
callers.push(entry);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
nodesVisited = ctx.nodesVisited;
|
|
557
|
+
truncated = ctx.truncated;
|
|
558
|
+
return callers;
|
|
559
|
+
};
|
|
560
|
+
const results = await findCallersOf(symbol, depth);
|
|
561
|
+
return {
|
|
562
|
+
mode: 'callers',
|
|
563
|
+
symbol,
|
|
564
|
+
truncated,
|
|
565
|
+
results,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
// ===== CALLEES =====
|
|
569
|
+
// @plan PLAN-20260211-ASTGREP.P09
|
|
570
|
+
deduplicateCallRanges(callMatches) {
|
|
571
|
+
const ranges = callMatches.map((c) => ({
|
|
572
|
+
node: c,
|
|
573
|
+
start: c.range().start.index,
|
|
574
|
+
end: c.range().end.index,
|
|
575
|
+
}));
|
|
576
|
+
ranges.sort((a, b) => a.start - b.start !== 0 ? a.start - b.start : b.end - a.end);
|
|
577
|
+
const outermost = [];
|
|
578
|
+
for (const r of ranges) {
|
|
579
|
+
const isContained = outermost.some((o) => r.start >= o.start && r.end <= o.end);
|
|
580
|
+
if (!isContained)
|
|
581
|
+
outermost.push(r);
|
|
582
|
+
}
|
|
583
|
+
return outermost;
|
|
584
|
+
}
|
|
585
|
+
async findCalleesOfFile(file, lang, sym, workspaceRoot, visited, ctx) {
|
|
586
|
+
if (ctx.nodesVisited >= ctx.maxNodes)
|
|
587
|
+
return [];
|
|
588
|
+
const parsed = await this.parseFile(file, lang);
|
|
589
|
+
if (!parsed)
|
|
590
|
+
return [];
|
|
591
|
+
const relPath = makeRelative(file, workspaceRoot);
|
|
592
|
+
const results = [];
|
|
593
|
+
try {
|
|
594
|
+
const methodMatches = parsed.root.findAll({
|
|
595
|
+
rule: {
|
|
596
|
+
kind: 'method_definition',
|
|
597
|
+
has: {
|
|
598
|
+
kind: 'property_identifier',
|
|
599
|
+
regex: `^${this.escapeRegex(sym)}$`,
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
for (const methodNode of methodMatches) {
|
|
604
|
+
const callMatches = methodNode.findAll({
|
|
605
|
+
rule: { kind: 'call_expression' },
|
|
606
|
+
});
|
|
607
|
+
const outermost = this.deduplicateCallRanges(callMatches);
|
|
608
|
+
for (const { node } of outermost) {
|
|
609
|
+
const callText = node.text().substring(0, 200);
|
|
610
|
+
const key = `${callText}@${relPath}`;
|
|
611
|
+
// eslint-disable-next-line sonarjs/nested-control-flow -- Dedup check inside nested iteration over AST matches
|
|
612
|
+
if (visited.has(key))
|
|
613
|
+
continue;
|
|
614
|
+
visited.add(key);
|
|
615
|
+
ctx.nodesVisited++;
|
|
616
|
+
results.push({
|
|
617
|
+
text: callText,
|
|
618
|
+
file: relPath,
|
|
619
|
+
line: node.range().start.line + 1,
|
|
620
|
+
calleeNode: node,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
/* skip */
|
|
627
|
+
}
|
|
628
|
+
return results;
|
|
629
|
+
}
|
|
630
|
+
async executeCallees(symbol, lang, searchPath, workspaceRoot, depth, maxNodes, signal) {
|
|
631
|
+
const files = await this.getFiles(searchPath, lang);
|
|
632
|
+
const visited = new Set();
|
|
633
|
+
let nodesVisited = 0;
|
|
634
|
+
let truncated = false;
|
|
635
|
+
const findCalleesOf = async (sym, currentDepth) => {
|
|
636
|
+
if (currentDepth <= 0 || nodesVisited >= maxNodes || signal.aborted) {
|
|
637
|
+
if (nodesVisited >= maxNodes)
|
|
638
|
+
truncated = true;
|
|
639
|
+
return [];
|
|
640
|
+
}
|
|
641
|
+
const callees = [];
|
|
642
|
+
const ctx = { nodesVisited, maxNodes };
|
|
643
|
+
for (const file of files) {
|
|
644
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Signal may be aborted asynchronously between recursive calls while scanning files.
|
|
645
|
+
if (signal.aborted)
|
|
646
|
+
break;
|
|
647
|
+
const calleeResults = await this.findCalleesOfFile(file, lang, sym, workspaceRoot, visited, ctx);
|
|
648
|
+
for (const r of calleeResults) {
|
|
649
|
+
const entry = {
|
|
650
|
+
text: r.text,
|
|
651
|
+
file: r.file,
|
|
652
|
+
line: r.line,
|
|
653
|
+
};
|
|
654
|
+
const calleeName = r.calleeNode
|
|
655
|
+
? this.extractCalleeName(r.calleeNode)
|
|
656
|
+
: null;
|
|
657
|
+
if (currentDepth > 1 &&
|
|
658
|
+
ctx.nodesVisited < maxNodes &&
|
|
659
|
+
calleeName &&
|
|
660
|
+
calleeName !== sym) {
|
|
661
|
+
nodesVisited = ctx.nodesVisited;
|
|
662
|
+
entry.callees = await findCalleesOf(calleeName, currentDepth - 1);
|
|
663
|
+
ctx.nodesVisited = nodesVisited;
|
|
664
|
+
}
|
|
665
|
+
callees.push(entry);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
nodesVisited = ctx.nodesVisited;
|
|
669
|
+
return callees;
|
|
670
|
+
};
|
|
671
|
+
const results = await findCalleesOf(symbol, depth);
|
|
672
|
+
return {
|
|
673
|
+
mode: 'callees',
|
|
674
|
+
symbol,
|
|
675
|
+
truncated,
|
|
676
|
+
results,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
// ===== REFERENCES =====
|
|
680
|
+
// @plan PLAN-20260211-ASTGREP.P10
|
|
681
|
+
searchDirectCallReferences(parsed, symbol, relPath, addResult) {
|
|
682
|
+
try {
|
|
683
|
+
const memberCalls = parsed.root.findAll(`$OBJ.${symbol}($$$ARGS)`);
|
|
684
|
+
for (const m of memberCalls) {
|
|
685
|
+
addResult('Direct calls', relPath, m.range().start.line + 1, m.text());
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
/* skip */
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
const standaloneCalls = parsed.root.findAll(`${symbol}($$$ARGS)`);
|
|
693
|
+
for (const m of standaloneCalls) {
|
|
694
|
+
addResult('Direct calls', relPath, m.range().start.line + 1, m.text());
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
catch {
|
|
698
|
+
/* skip */
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
searchInstantiationReferences(parsed, symbol, relPath, addResult) {
|
|
702
|
+
try {
|
|
703
|
+
const news = parsed.root.findAll(`new ${symbol}($$$ARGS)`);
|
|
704
|
+
for (const m of news) {
|
|
705
|
+
addResult('Instantiations', relPath, m.range().start.line + 1, m.text());
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
catch {
|
|
709
|
+
/* skip */
|
|
710
|
+
}
|
|
711
|
+
try {
|
|
712
|
+
const lowerSymbol = symbol.charAt(0).toLowerCase() + symbol.slice(1);
|
|
713
|
+
const instanceCalls = parsed.root.findAll({
|
|
714
|
+
rule: {
|
|
715
|
+
kind: 'call_expression',
|
|
716
|
+
has: {
|
|
717
|
+
kind: 'member_expression',
|
|
718
|
+
has: {
|
|
719
|
+
kind: 'identifier',
|
|
720
|
+
regex: `(?i)${this.escapeRegex(lowerSymbol)}|${this.escapeRegex(symbol)}`,
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
for (const m of instanceCalls) {
|
|
726
|
+
addResult('Instance method calls (heuristic)', relPath, m.range().start.line + 1, m.text());
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
/* skip */
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
searchTypeAndHeritageReferences(parsed, symbol, relPath, addResult) {
|
|
734
|
+
try {
|
|
735
|
+
const typeRefs = parsed.root.findAll({
|
|
736
|
+
rule: {
|
|
737
|
+
kind: 'type_annotation',
|
|
738
|
+
has: {
|
|
739
|
+
kind: 'type_identifier',
|
|
740
|
+
regex: `^${this.escapeRegex(symbol)}$`,
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
for (const m of typeRefs) {
|
|
745
|
+
addResult('Type annotations', relPath, m.range().start.line + 1, m.text());
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
catch {
|
|
749
|
+
/* skip */
|
|
750
|
+
}
|
|
751
|
+
try {
|
|
752
|
+
const heritage = parsed.root.findAll(`class $NAME extends ${symbol} { $$$BODY }`);
|
|
753
|
+
for (const m of heritage) {
|
|
754
|
+
addResult('Extends/Implements', relPath, m.range().start.line + 1, `class ${m.getMatch('NAME')?.text()} extends ${symbol}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
/* skip */
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
const implHeritage = parsed.root.findAll(`class $NAME implements ${symbol} { $$$BODY }`);
|
|
762
|
+
for (const m of implHeritage) {
|
|
763
|
+
addResult('Extends/Implements', relPath, m.range().start.line + 1, `class ${m.getMatch('NAME')?.text()} implements ${symbol}`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
catch {
|
|
767
|
+
/* skip */
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
searchImportReferences(parsed, symbol, relPath, addResult) {
|
|
771
|
+
try {
|
|
772
|
+
const imports = parsed.root.findAll({
|
|
773
|
+
rule: {
|
|
774
|
+
kind: 'import_specifier',
|
|
775
|
+
has: { kind: 'identifier', regex: `^${this.escapeRegex(symbol)}$` },
|
|
776
|
+
},
|
|
777
|
+
});
|
|
778
|
+
for (const m of imports) {
|
|
779
|
+
addResult('Imports', relPath, m.range().start.line + 1, m.text());
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
catch {
|
|
783
|
+
/* skip */
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
async executeReferences(symbol, lang, searchPath, workspaceRoot, signal) {
|
|
787
|
+
const files = await this.getFiles(searchPath, lang);
|
|
788
|
+
const categories = {
|
|
789
|
+
'Direct calls': [],
|
|
790
|
+
'Instance method calls (heuristic)': [],
|
|
791
|
+
Instantiations: [],
|
|
792
|
+
'Type annotations': [],
|
|
793
|
+
'Extends/Implements': [],
|
|
794
|
+
Imports: [],
|
|
795
|
+
};
|
|
796
|
+
const seen = new Set();
|
|
797
|
+
const addResult = (category, file, line, text) => {
|
|
798
|
+
const key = `${category}:${file}:${line}`;
|
|
799
|
+
if (seen.has(key))
|
|
800
|
+
return;
|
|
801
|
+
seen.add(key);
|
|
802
|
+
categories[category].push({ file, line, text: text.substring(0, 200) });
|
|
803
|
+
};
|
|
804
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
805
|
+
for (const file of files) {
|
|
806
|
+
if (signal.aborted)
|
|
807
|
+
break;
|
|
808
|
+
const parsed = await this.parseFile(file, lang);
|
|
809
|
+
if (!parsed)
|
|
810
|
+
continue;
|
|
811
|
+
const relPath = makeRelative(file, workspaceRoot);
|
|
812
|
+
this.searchDirectCallReferences(parsed, symbol, relPath, addResult);
|
|
813
|
+
this.searchInstantiationReferences(parsed, symbol, relPath, addResult);
|
|
814
|
+
this.searchTypeAndHeritageReferences(parsed, symbol, relPath, addResult);
|
|
815
|
+
this.searchImportReferences(parsed, symbol, relPath, addResult);
|
|
816
|
+
}
|
|
817
|
+
const counts = {};
|
|
818
|
+
for (const [cat, items] of Object.entries(categories)) {
|
|
819
|
+
counts[cat] = items.length;
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
mode: 'references',
|
|
823
|
+
symbol,
|
|
824
|
+
truncated: false,
|
|
825
|
+
results: { categories, counts },
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
// ===== DEPENDENCIES =====
|
|
829
|
+
// @plan PLAN-20260211-ASTGREP.P10
|
|
830
|
+
collectNamedAndDefaultImports(parsed, relPath, imports) {
|
|
831
|
+
try {
|
|
832
|
+
const named = parsed.root.findAll(`import { $$$NAMES } from $SOURCE`);
|
|
833
|
+
for (const m of named) {
|
|
834
|
+
const src = m.getMatch('SOURCE');
|
|
835
|
+
if (src) {
|
|
836
|
+
imports.push({
|
|
837
|
+
file: relPath,
|
|
838
|
+
line: m.range().start.line + 1,
|
|
839
|
+
source: src.text().replace(/['"]/g, ''),
|
|
840
|
+
kind: 'named',
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
catch {
|
|
846
|
+
/* skip */
|
|
847
|
+
}
|
|
848
|
+
try {
|
|
849
|
+
const defaults = parsed.root.findAll(`import $DEFAULT from $SOURCE`);
|
|
850
|
+
for (const m of defaults) {
|
|
851
|
+
const src = m.getMatch('SOURCE');
|
|
852
|
+
if (src) {
|
|
853
|
+
imports.push({
|
|
854
|
+
file: relPath,
|
|
855
|
+
line: m.range().start.line + 1,
|
|
856
|
+
source: src.text().replace(/['"]/g, ''),
|
|
857
|
+
kind: 'default',
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
catch {
|
|
863
|
+
/* skip */
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
collectDynamicAndReexports(parsed, relPath, imports) {
|
|
867
|
+
try {
|
|
868
|
+
const dynamic = parsed.root.findAll({
|
|
869
|
+
rule: {
|
|
870
|
+
kind: 'call_expression',
|
|
871
|
+
has: { kind: 'import' },
|
|
872
|
+
},
|
|
873
|
+
});
|
|
874
|
+
for (const m of dynamic) {
|
|
875
|
+
imports.push({
|
|
876
|
+
file: relPath,
|
|
877
|
+
line: m.range().start.line + 1,
|
|
878
|
+
source: m.text(),
|
|
879
|
+
kind: 'dynamic',
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
catch {
|
|
884
|
+
/* skip */
|
|
885
|
+
}
|
|
886
|
+
try {
|
|
887
|
+
const reexports = parsed.root.findAll({
|
|
888
|
+
rule: {
|
|
889
|
+
kind: 'export_statement',
|
|
890
|
+
has: { kind: 'string', regex: '.' },
|
|
891
|
+
},
|
|
892
|
+
});
|
|
893
|
+
for (const m of reexports) {
|
|
894
|
+
if (m.text().includes('from')) {
|
|
895
|
+
imports.push({
|
|
896
|
+
file: relPath,
|
|
897
|
+
line: m.range().start.line + 1,
|
|
898
|
+
source: m.text().substring(0, 200),
|
|
899
|
+
kind: 'reexport',
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
catch {
|
|
905
|
+
/* skip */
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
collectFileImports(parsed, relPath, imports) {
|
|
909
|
+
this.collectNamedAndDefaultImports(parsed, relPath, imports);
|
|
910
|
+
this.collectDynamicAndReexports(parsed, relPath, imports);
|
|
911
|
+
}
|
|
912
|
+
async findReverseImports(searchPath, workspaceRoot, lang, signal) {
|
|
913
|
+
const reverseImports = [];
|
|
914
|
+
const allFiles = await this.getFiles(workspaceRoot, lang);
|
|
915
|
+
const targetRel = makeRelative(searchPath, workspaceRoot);
|
|
916
|
+
const targetBasename = path.basename(searchPath).replace(/\.\w+$/, '');
|
|
917
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
918
|
+
for (const file of allFiles) {
|
|
919
|
+
if (signal.aborted)
|
|
920
|
+
break;
|
|
921
|
+
const parsed = await this.parseFile(file, lang);
|
|
922
|
+
if (!parsed)
|
|
923
|
+
continue;
|
|
924
|
+
const relPath = makeRelative(file, workspaceRoot);
|
|
925
|
+
if (relPath === targetRel)
|
|
926
|
+
continue;
|
|
927
|
+
const content = parsed.content || '';
|
|
928
|
+
if (content.includes(targetBasename)) {
|
|
929
|
+
reverseImports.push(...this.collectImportMatches(parsed, relPath, targetBasename));
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return reverseImports;
|
|
933
|
+
}
|
|
934
|
+
collectImportMatches(parsed, relPath, targetBasename) {
|
|
935
|
+
const imports = [];
|
|
936
|
+
try {
|
|
937
|
+
const allImports = parsed.root.findAll({
|
|
938
|
+
rule: { kind: 'import_statement' },
|
|
939
|
+
});
|
|
940
|
+
for (const m of allImports) {
|
|
941
|
+
const text = m.text();
|
|
942
|
+
if (text.includes(targetBasename)) {
|
|
943
|
+
imports.push({
|
|
944
|
+
file: relPath,
|
|
945
|
+
line: m.range().start.line + 1,
|
|
946
|
+
source: text.substring(0, 200),
|
|
947
|
+
kind: 'import',
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
catch {
|
|
953
|
+
/* skip */
|
|
954
|
+
}
|
|
955
|
+
return imports;
|
|
956
|
+
}
|
|
957
|
+
async executeDependencies(searchPath, lang, workspaceRoot, reverse, signal) {
|
|
958
|
+
const files = await this.getFiles(searchPath, lang);
|
|
959
|
+
const imports = [];
|
|
960
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
961
|
+
for (const file of files) {
|
|
962
|
+
if (signal.aborted)
|
|
963
|
+
break;
|
|
964
|
+
const parsed = await this.parseFile(file, lang);
|
|
965
|
+
if (!parsed)
|
|
966
|
+
continue;
|
|
967
|
+
const relPath = makeRelative(file, workspaceRoot);
|
|
968
|
+
this.collectFileImports(parsed, relPath, imports);
|
|
969
|
+
}
|
|
970
|
+
const reverseImports = reverse
|
|
971
|
+
? await this.findReverseImports(searchPath, workspaceRoot, lang, signal)
|
|
972
|
+
: [];
|
|
973
|
+
return {
|
|
974
|
+
mode: 'dependencies',
|
|
975
|
+
truncated: false,
|
|
976
|
+
results: {
|
|
977
|
+
imports,
|
|
978
|
+
reverseImports: reverse ? reverseImports : undefined,
|
|
979
|
+
},
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
// ===== EXPORTS =====
|
|
983
|
+
// @plan PLAN-20260211-ASTGREP.P10
|
|
984
|
+
async executeExports(searchPath, lang, workspaceRoot, signal) {
|
|
985
|
+
const files = await this.getFiles(searchPath, lang);
|
|
986
|
+
const exports = [];
|
|
987
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
988
|
+
for (const file of files) {
|
|
989
|
+
if (signal.aborted)
|
|
990
|
+
break;
|
|
991
|
+
const parsed = await this.parseFile(file, lang);
|
|
992
|
+
if (!parsed)
|
|
993
|
+
continue;
|
|
994
|
+
const relPath = makeRelative(file, workspaceRoot);
|
|
995
|
+
try {
|
|
996
|
+
const exportNodes = parsed.root.findAll({
|
|
997
|
+
rule: { kind: 'export_statement' },
|
|
998
|
+
});
|
|
999
|
+
for (const m of exportNodes) {
|
|
1000
|
+
const text = m.text();
|
|
1001
|
+
let kind = 'export';
|
|
1002
|
+
// eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
1003
|
+
if (/^export\s+default\b/.test(text))
|
|
1004
|
+
kind = 'default';
|
|
1005
|
+
else if (text.includes('class'))
|
|
1006
|
+
kind = 'class';
|
|
1007
|
+
else if (text.includes('function'))
|
|
1008
|
+
kind = 'function';
|
|
1009
|
+
else if (text.includes('const') ||
|
|
1010
|
+
text.includes('let') ||
|
|
1011
|
+
text.includes('var'))
|
|
1012
|
+
kind = 'const';
|
|
1013
|
+
else if (text.includes('interface'))
|
|
1014
|
+
kind = 'interface';
|
|
1015
|
+
else if (text.includes('type '))
|
|
1016
|
+
kind = 'type';
|
|
1017
|
+
else if (text.includes('from'))
|
|
1018
|
+
kind = 'reexport';
|
|
1019
|
+
exports.push({
|
|
1020
|
+
file: relPath,
|
|
1021
|
+
line: m.range().start.line + 1,
|
|
1022
|
+
text: text.substring(0, 200),
|
|
1023
|
+
kind,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
catch {
|
|
1028
|
+
/* skip */
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return {
|
|
1032
|
+
mode: 'exports',
|
|
1033
|
+
truncated: false,
|
|
1034
|
+
results: exports,
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
export class StructuralAnalysisTool extends BaseDeclarativeTool {
|
|
1039
|
+
host;
|
|
1040
|
+
static Name = 'structural_analysis';
|
|
1041
|
+
constructor(host) {
|
|
1042
|
+
super(StructuralAnalysisTool.Name, 'StructuralAnalysis', 'Performs multi-hop AST-based code analysis: call graphs, type hierarchies, symbol references, ' +
|
|
1043
|
+
'module dependencies, and exports. This is name-based (not type-resolved) analysis. ' +
|
|
1044
|
+
'Use for understanding code relationships. Unlike ast_grep (single-query), this chains multiple queries.', Kind.Search, {
|
|
1045
|
+
properties: {
|
|
1046
|
+
mode: {
|
|
1047
|
+
description: `Analysis mode: ${VALID_MODES.join(', ')}`,
|
|
1048
|
+
type: 'string',
|
|
1049
|
+
enum: [...VALID_MODES],
|
|
1050
|
+
},
|
|
1051
|
+
language: {
|
|
1052
|
+
description: 'Programming language (e.g., typescript, python)',
|
|
1053
|
+
type: 'string',
|
|
1054
|
+
},
|
|
1055
|
+
path: {
|
|
1056
|
+
description: 'Directory to search. Defaults to workspace root.',
|
|
1057
|
+
type: 'string',
|
|
1058
|
+
},
|
|
1059
|
+
symbol: {
|
|
1060
|
+
description: 'Symbol name for callers/callees/definitions/hierarchy/references modes.',
|
|
1061
|
+
type: 'string',
|
|
1062
|
+
},
|
|
1063
|
+
depth: {
|
|
1064
|
+
description: 'Recursion depth for callers/callees. Default 1, max 5.',
|
|
1065
|
+
type: 'number',
|
|
1066
|
+
},
|
|
1067
|
+
maxNodes: {
|
|
1068
|
+
description: 'Max symbols to visit during recursive traversal. Default 50.',
|
|
1069
|
+
type: 'number',
|
|
1070
|
+
},
|
|
1071
|
+
target: {
|
|
1072
|
+
description: 'File/directory for dependencies/exports modes.',
|
|
1073
|
+
type: 'string',
|
|
1074
|
+
},
|
|
1075
|
+
reverse: {
|
|
1076
|
+
description: 'For dependencies mode: also find what imports the target.',
|
|
1077
|
+
type: 'boolean',
|
|
1078
|
+
},
|
|
1079
|
+
},
|
|
1080
|
+
required: ['mode', 'language'],
|
|
1081
|
+
type: 'object',
|
|
1082
|
+
});
|
|
1083
|
+
this.host = host;
|
|
1084
|
+
}
|
|
1085
|
+
createInvocation(params, messageBus) {
|
|
1086
|
+
return new StructuralAnalysisInvocation(this.host, params, messageBus);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
//# sourceMappingURL=structural-analysis.js.map
|