diff --git a/src/serena/jetbrains/jetbrains_plugin_client.py b/src/serena/jetbrains/jetbrains_plugin_client.py index 263bc8a9..c662ec76 100644 --- a/src/serena/jetbrains/jetbrains_plugin_client.py +++ b/src/serena/jetbrains/jetbrains_plugin_client.py @@ -661,6 +661,54 @@ class JetBrainsPluginClient(ToStringMixin): } return self._make_request("POST", "/debugReplClose", request_data) + def run_inspections( + self, + relative_path: str, + min_severity: str | None = None, + inspection_names: list[str] | None = None, + start_line: int | None = None, + end_line: int | None = None, + ) -> jb.RunInspectionsResponse: + """ + Runs IDE inspections on the given file and returns the results. + + :param relative_path: the relative path to the file to inspect + :param min_severity: minimum severity level to include (e.g. "WARNING", "ERROR") + :param inspection_names: optional list of specific inspection names to run + :param start_line: optional start line to restrict the inspection range + :param end_line: optional end line to restrict the inspection range + """ + request_data: dict[str, Any] = { + "relativePath": relative_path, + } + if min_severity is not None: + request_data["minSeverity"] = min_severity + if inspection_names is not None: + request_data["inspectionNames"] = inspection_names + if start_line is not None: + request_data["startLine"] = start_line + if end_line is not None: + request_data["endLine"] = end_line + return cast(jb.RunInspectionsResponse, self._make_request("POST", "/runInspections", request_data)) + + def list_inspections( + self, + language: str | None = None, + group_path_contains: str | None = None, + ) -> jb.ListInspectionsResponse: + """ + Lists available IDE inspections, optionally filtered by language and/or group path. + + :param language: optional language to filter inspections by (e.g. "Java", "Python") + :param group_path_contains: optional substring to filter inspection group paths + """ + request_data: dict[str, Any] = {} + if language is not None: + request_data["language"] = language + if group_path_contains is not None: + request_data["groupPathContains"] = group_path_contains + return cast(jb.ListInspectionsResponse, self._make_request("POST", "/listInspections", request_data)) + def close(self) -> None: self._session.close() diff --git a/src/serena/jetbrains/jetbrains_types.py b/src/serena/jetbrains/jetbrains_types.py index ead4eb55..ce488ea2 100644 --- a/src/serena/jetbrains/jetbrains_types.py +++ b/src/serena/jetbrains/jetbrains_types.py @@ -89,3 +89,30 @@ class TypeHierarchyNodeDTO(TypedDict): class TypeHierarchyResponse(TypedDict): hierarchy: NotRequired[list[TypeHierarchyNodeDTO]] num_levels_not_included: NotRequired[int] + + +class InspectionResultDTO(TypedDict): + inspection_name: str + severity: str + message: str + relative_path: str + start_line: int + end_line: int + start_col: NotRequired[int] + end_col: NotRequired[int] + quick_fix_available: NotRequired[bool] + + +class RunInspectionsResponse(TypedDict): + inspections: list[InspectionResultDTO] + + +class InspectionInfoDTO(TypedDict): + name: str + group_path: str + language: NotRequired[str] + description: NotRequired[str] + + +class ListInspectionsResponse(TypedDict): + inspections: list[InspectionInfoDTO] diff --git a/src/serena/resources/config/internal_modes/jetbrains.yml b/src/serena/resources/config/internal_modes/jetbrains.yml index 34baf6ce..1932906c 100644 --- a/src/serena/resources/config/internal_modes/jetbrains.yml +++ b/src/serena/resources/config/internal_modes/jetbrains.yml @@ -25,3 +25,5 @@ included_optional_tools: - jet_brains_safe_delete - jet_brains_debug - serena_info + - jet_brains_run_inspections + - jet_brains_list_inspections diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 0664fb03..74a05359 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -595,3 +595,76 @@ class JetBrainsDebugTool(Tool, ToolMarkerOptional, ToolMarkerBeta): else: response = client.debug_close(repl_key=repl_key) return response.get("result", str(response)) + + +class JetBrainsRunInspectionsTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional): + """ + Runs JetBrains IDE inspections on a file and returns the results. + """ + + def apply( + self, + relative_path: str, + min_severity: str | None = None, + inspection_names: list[str] | None = None, + start_line: int | None = None, + end_line: int | None = None, + max_answer_chars: int = -1, + ) -> str: + """ + Runs IDE inspections (code analysis) on the given file and returns the problems found. + This leverages the full power of JetBrains' static analysis engine, including language-specific + inspections, type checking, potential bugs, code style issues, and more. + + :param relative_path: the relative path to the file to inspect. + :param min_severity: minimum severity level to include in results (e.g. "ERROR", "WARNING", "WEAK_WARNING", "INFO"). + If not specified, all severities are returned. + :param inspection_names: optional list of specific inspection names to run (e.g. ["UnusedImport", "TypeMismatch"]). + If not specified, all applicable inspections are run. + :param start_line: optional 1-based start line to restrict the inspection range. + :param end_line: optional 1-based end line to restrict the inspection range. + :param max_answer_chars: max characters for the JSON result. If exceeded, no content is returned. + -1 means the default value from the config will be used. + :return: JSON string with inspection results including severity, message, and location. + """ + with JetBrainsPluginClient.from_project(self.project) as client: + response_dict = client.run_inspections( + relative_path=relative_path, + min_severity=min_severity, + inspection_names=inspection_names, + start_line=start_line, + end_line=end_line, + ) + result = self._to_json(response_dict) + return self._limit_length(result, max_answer_chars) + + +class JetBrainsListInspectionsTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional): + """ + Lists available JetBrains IDE inspections, optionally filtered by language or group. + """ + + def apply( + self, + language: str | None = None, + group_path_contains: str | None = None, + max_answer_chars: int = -1, + ) -> str: + """ + Lists the available IDE inspections. Use this to discover which inspections can be passed + to the run_inspections tool's `inspection_names` parameter. + + :param language: optional language to filter by (e.g. "Java", "Python", "Kotlin"). + :param group_path_contains: optional substring to match against the inspection group path + (e.g. "probable bugs", "code style"). + :param max_answer_chars: max characters for the JSON result. If exceeded, no content is returned. + -1 means the default value from the config will be used. + :return: JSON string with the list of available inspections including name, group path, and language. + """ + with JetBrainsPluginClient.from_project(self.project) as client: + response_dict = client.list_inspections( + language=language, + group_path_contains=group_path_contains, + ) + result = self._to_json(response_dict) + return self._limit_length(result, max_answer_chars)