mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 13:51:16 +00:00
[ty] Add LSP debug information command (#20379)
Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
parent
12086dfa69
commit
eb354608d2
10 changed files with 458 additions and 1 deletions
|
@ -463,7 +463,7 @@ impl From<&'static LintMetadata> for LintEntry {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq, get_size2::GetSize)]
|
||||
#[derive(Clone, Default, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub struct RuleSelection {
|
||||
/// Map with the severity for each enabled lint rule.
|
||||
///
|
||||
|
@ -541,6 +541,35 @@ impl RuleSelection {
|
|||
}
|
||||
}
|
||||
|
||||
// The default `LintId` debug implementation prints the entire lint metadata.
|
||||
// This is way too verbose.
|
||||
impl fmt::Debug for RuleSelection {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let lints = self.lints.iter().sorted_by_key(|(lint, _)| lint.name);
|
||||
|
||||
if f.alternate() {
|
||||
let mut f = f.debug_map();
|
||||
|
||||
for (lint, (severity, source)) in lints {
|
||||
f.entry(
|
||||
&lint.name().as_str(),
|
||||
&format_args!("{severity:?} ({source:?})"),
|
||||
);
|
||||
}
|
||||
|
||||
f.finish()
|
||||
} else {
|
||||
let mut f = f.debug_set();
|
||||
|
||||
for (lint, _) in lints {
|
||||
f.entry(&lint.name());
|
||||
}
|
||||
|
||||
f.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub enum LintSource {
|
||||
/// The user didn't enable the rule explicitly, instead it's enabled by default.
|
||||
|
|
|
@ -10,6 +10,8 @@ use lsp_types::{
|
|||
|
||||
use crate::PositionEncoding;
|
||||
use crate::session::GlobalSettings;
|
||||
use lsp_types as types;
|
||||
use std::str::FromStr;
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Represents the resolved client capabilities for the language server.
|
||||
|
@ -37,6 +39,36 @@ bitflags::bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub(crate) enum SupportedCommand {
|
||||
Debug,
|
||||
}
|
||||
|
||||
impl SupportedCommand {
|
||||
/// Returns the identifier of the command.
|
||||
const fn identifier(self) -> &'static str {
|
||||
match self {
|
||||
SupportedCommand::Debug => "ty.printDebugInformation",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all the commands that the server currently supports.
|
||||
const fn all() -> [SupportedCommand; 1] {
|
||||
[SupportedCommand::Debug]
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SupportedCommand {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(name: &str) -> anyhow::Result<Self, Self::Err> {
|
||||
Ok(match name {
|
||||
"ty.printDebugInformation" => Self::Debug,
|
||||
_ => return Err(anyhow::anyhow!("Invalid command `{name}`")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolvedClientCapabilities {
|
||||
/// Returns `true` if the client supports workspace diagnostic refresh.
|
||||
pub(crate) const fn supports_workspace_diagnostic_refresh(self) -> bool {
|
||||
|
@ -319,6 +351,15 @@ pub(crate) fn server_capabilities(
|
|||
|
||||
ServerCapabilities {
|
||||
position_encoding: Some(position_encoding.into()),
|
||||
execute_command_provider: Some(types::ExecuteCommandOptions {
|
||||
commands: SupportedCommand::all()
|
||||
.map(|command| command.identifier().to_string())
|
||||
.to_vec(),
|
||||
work_done_progress_options: WorkDoneProgressOptions {
|
||||
work_done_progress: Some(false),
|
||||
},
|
||||
}),
|
||||
|
||||
diagnostic_provider,
|
||||
text_document_sync: Some(TextDocumentSyncCapability::Options(
|
||||
TextDocumentSyncOptions {
|
||||
|
|
|
@ -32,6 +32,7 @@ pub(super) fn request(req: server::Request) -> Task {
|
|||
let id = req.id.clone();
|
||||
|
||||
match req.method.as_str() {
|
||||
requests::ExecuteCommand::METHOD => sync_request_task::<requests::ExecuteCommand>(req),
|
||||
requests::DocumentDiagnosticRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::DocumentDiagnosticRequestHandler,
|
||||
>(
|
||||
|
|
|
@ -2,6 +2,7 @@ mod completion;
|
|||
mod diagnostic;
|
||||
mod doc_highlights;
|
||||
mod document_symbols;
|
||||
mod execute_command;
|
||||
mod goto_declaration;
|
||||
mod goto_definition;
|
||||
mod goto_references;
|
||||
|
@ -22,6 +23,7 @@ pub(super) use completion::CompletionRequestHandler;
|
|||
pub(super) use diagnostic::DocumentDiagnosticRequestHandler;
|
||||
pub(super) use doc_highlights::DocumentHighlightRequestHandler;
|
||||
pub(super) use document_symbols::DocumentSymbolRequestHandler;
|
||||
pub(super) use execute_command::ExecuteCommand;
|
||||
pub(super) use goto_declaration::GotoDeclarationRequestHandler;
|
||||
pub(super) use goto_definition::GotoDefinitionRequestHandler;
|
||||
pub(super) use goto_references::ReferencesRequestHandler;
|
||||
|
|
76
crates/ty_server/src/server/api/requests/execute_command.rs
Normal file
76
crates/ty_server/src/server/api/requests/execute_command.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use crate::capabilities::SupportedCommand;
|
||||
use crate::server;
|
||||
use crate::server::api::LSPResult;
|
||||
use crate::server::api::RequestHandler;
|
||||
use crate::server::api::traits::SyncRequestHandler;
|
||||
use crate::session::Session;
|
||||
use crate::session::client::Client;
|
||||
use lsp_server::ErrorCode;
|
||||
use lsp_types::{self as types, request as req};
|
||||
use std::fmt::Write;
|
||||
use std::str::FromStr;
|
||||
use ty_project::Db;
|
||||
|
||||
pub(crate) struct ExecuteCommand;
|
||||
|
||||
impl RequestHandler for ExecuteCommand {
|
||||
type RequestType = req::ExecuteCommand;
|
||||
}
|
||||
|
||||
impl SyncRequestHandler for ExecuteCommand {
|
||||
fn run(
|
||||
session: &mut Session,
|
||||
_client: &Client,
|
||||
params: types::ExecuteCommandParams,
|
||||
) -> server::Result<Option<serde_json::Value>> {
|
||||
let command = SupportedCommand::from_str(¶ms.command)
|
||||
.with_failure_code(ErrorCode::InvalidParams)?;
|
||||
|
||||
match command {
|
||||
SupportedCommand::Debug => Ok(Some(serde_json::Value::String(
|
||||
debug_information(session).with_failure_code(ErrorCode::InternalError)?,
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a string with detailed memory usage.
|
||||
fn debug_information(session: &Session) -> crate::Result<String> {
|
||||
let mut buffer = String::new();
|
||||
|
||||
writeln!(
|
||||
buffer,
|
||||
"Client capabilities: {:#?}",
|
||||
session.client_capabilities()
|
||||
)?;
|
||||
writeln!(
|
||||
buffer,
|
||||
"Position encoding: {:#?}",
|
||||
session.position_encoding()
|
||||
)?;
|
||||
writeln!(buffer, "Global settings: {:#?}", session.global_settings())?;
|
||||
writeln!(
|
||||
buffer,
|
||||
"Open text documents: {}",
|
||||
session.text_document_keys().count()
|
||||
)?;
|
||||
writeln!(buffer)?;
|
||||
|
||||
for (root, workspace) in session.workspaces() {
|
||||
writeln!(buffer, "Workspace {root} ({})", workspace.url())?;
|
||||
writeln!(buffer, "Settings: {:#?}", workspace.settings())?;
|
||||
writeln!(buffer)?;
|
||||
}
|
||||
|
||||
for db in session.project_dbs() {
|
||||
writeln!(buffer, "Project at {}", db.project().root(db))?;
|
||||
writeln!(buffer, "Settings: {:#?}", db.project().settings(db))?;
|
||||
writeln!(buffer)?;
|
||||
writeln!(
|
||||
buffer,
|
||||
"Memory report:\n{}",
|
||||
db.salsa_memory_dump().display_full()
|
||||
)?;
|
||||
}
|
||||
Ok(buffer)
|
||||
}
|
55
crates/ty_server/tests/e2e/commands.rs
Normal file
55
crates/ty_server/tests/e2e/commands.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use anyhow::Result;
|
||||
use lsp_types::{ExecuteCommandParams, WorkDoneProgressParams, request::ExecuteCommand};
|
||||
use ruff_db::system::SystemPath;
|
||||
|
||||
use crate::{TestServer, TestServerBuilder};
|
||||
|
||||
// Sends an executeCommand request to the TestServer
|
||||
fn execute_command(
|
||||
server: &mut TestServer,
|
||||
command: String,
|
||||
arguments: Vec<serde_json::Value>,
|
||||
) -> anyhow::Result<Option<serde_json::Value>> {
|
||||
let params = ExecuteCommandParams {
|
||||
command,
|
||||
arguments,
|
||||
work_done_progress_params: WorkDoneProgressParams::default(),
|
||||
};
|
||||
let id = server.send_request::<ExecuteCommand>(params);
|
||||
server.await_response::<ExecuteCommand>(&id)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debug_command() -> Result<()> {
|
||||
let workspace_root = SystemPath::new("src");
|
||||
let foo = SystemPath::new("src/foo.py");
|
||||
let foo_content = "\
|
||||
def foo() -> str:
|
||||
return 42
|
||||
";
|
||||
|
||||
let mut server = TestServerBuilder::new()?
|
||||
.with_workspace(workspace_root, None)?
|
||||
.with_file(foo, foo_content)?
|
||||
.enable_pull_diagnostics(false)
|
||||
.build()?
|
||||
.wait_until_workspaces_are_initialized()?;
|
||||
|
||||
let response = execute_command(&mut server, "ty.printDebugInformation".to_string(), vec![])?;
|
||||
let response = response.expect("expect server response");
|
||||
|
||||
let response = response
|
||||
.as_str()
|
||||
.expect("debug command to return a string response");
|
||||
|
||||
insta::with_settings!({
|
||||
filters =>vec![
|
||||
(r"\b[0-9]+.[0-9]+MB\b","[X.XXMB]"),
|
||||
(r"Workspace .+\)","Workspace XXX"),
|
||||
(r"Project at .+","Project at XXX"),
|
||||
]}, {
|
||||
insta::assert_snapshot!(response);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -27,6 +27,7 @@
|
|||
//! [`await_request`]: TestServer::await_request
|
||||
//! [`await_notification`]: TestServer::await_notification
|
||||
|
||||
mod commands;
|
||||
mod initialize;
|
||||
mod inlay_hints;
|
||||
mod publish_diagnostics;
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
---
|
||||
source: crates/ty_server/tests/e2e/commands.rs
|
||||
expression: response
|
||||
---
|
||||
Client capabilities: ResolvedClientCapabilities(
|
||||
WORKSPACE_CONFIGURATION,
|
||||
)
|
||||
Position encoding: UTF16
|
||||
Global settings: GlobalSettings {
|
||||
diagnostic_mode: OpenFilesOnly,
|
||||
experimental: ExperimentalSettings {
|
||||
rename: false,
|
||||
auto_import: false,
|
||||
},
|
||||
}
|
||||
Open text documents: 0
|
||||
|
||||
Workspace XXX
|
||||
Settings: WorkspaceSettings {
|
||||
disable_language_services: false,
|
||||
inlay_hints: InlayHintSettings {
|
||||
variable_types: true,
|
||||
call_argument_names: true,
|
||||
},
|
||||
overrides: None,
|
||||
}
|
||||
|
||||
Project at XXX
|
||||
Settings: Settings {
|
||||
rules: {
|
||||
"ambiguous-protocol-member": Warning (Default),
|
||||
"byte-string-type-annotation": Error (Default),
|
||||
"call-non-callable": Error (Default),
|
||||
"conflicting-argument-forms": Error (Default),
|
||||
"conflicting-declarations": Error (Default),
|
||||
"conflicting-metaclass": Error (Default),
|
||||
"cyclic-class-definition": Error (Default),
|
||||
"deprecated": Warning (Default),
|
||||
"duplicate-base": Error (Default),
|
||||
"duplicate-kw-only": Error (Default),
|
||||
"escape-character-in-forward-annotation": Error (Default),
|
||||
"fstring-type-annotation": Error (Default),
|
||||
"implicit-concatenated-string-type-annotation": Error (Default),
|
||||
"inconsistent-mro": Error (Default),
|
||||
"index-out-of-bounds": Error (Default),
|
||||
"instance-layout-conflict": Error (Default),
|
||||
"invalid-argument-type": Error (Default),
|
||||
"invalid-assignment": Error (Default),
|
||||
"invalid-attribute-access": Error (Default),
|
||||
"invalid-await": Error (Default),
|
||||
"invalid-base": Error (Default),
|
||||
"invalid-context-manager": Error (Default),
|
||||
"invalid-declaration": Error (Default),
|
||||
"invalid-exception-caught": Error (Default),
|
||||
"invalid-generic-class": Error (Default),
|
||||
"invalid-ignore-comment": Warning (Default),
|
||||
"invalid-key": Error (Default),
|
||||
"invalid-legacy-type-variable": Error (Default),
|
||||
"invalid-metaclass": Error (Default),
|
||||
"invalid-named-tuple": Error (Default),
|
||||
"invalid-overload": Error (Default),
|
||||
"invalid-parameter-default": Error (Default),
|
||||
"invalid-protocol": Error (Default),
|
||||
"invalid-raise": Error (Default),
|
||||
"invalid-return-type": Error (Default),
|
||||
"invalid-super-argument": Error (Default),
|
||||
"invalid-syntax-in-forward-annotation": Error (Default),
|
||||
"invalid-type-alias-type": Error (Default),
|
||||
"invalid-type-checking-constant": Error (Default),
|
||||
"invalid-type-form": Error (Default),
|
||||
"invalid-type-guard-call": Error (Default),
|
||||
"invalid-type-guard-definition": Error (Default),
|
||||
"invalid-type-variable-constraints": Error (Default),
|
||||
"missing-argument": Error (Default),
|
||||
"missing-typed-dict-key": Error (Default),
|
||||
"no-matching-overload": Error (Default),
|
||||
"non-subscriptable": Error (Default),
|
||||
"not-iterable": Error (Default),
|
||||
"parameter-already-assigned": Error (Default),
|
||||
"possibly-unbound-attribute": Warning (Default),
|
||||
"possibly-unbound-implicit-call": Warning (Default),
|
||||
"possibly-unbound-import": Warning (Default),
|
||||
"raw-string-type-annotation": Error (Default),
|
||||
"redundant-cast": Warning (Default),
|
||||
"static-assert-error": Error (Default),
|
||||
"subclass-of-final-class": Error (Default),
|
||||
"too-many-positional-arguments": Error (Default),
|
||||
"type-assertion-failure": Error (Default),
|
||||
"unavailable-implicit-super-arguments": Error (Default),
|
||||
"undefined-reveal": Warning (Default),
|
||||
"unknown-argument": Error (Default),
|
||||
"unknown-rule": Warning (Default),
|
||||
"unresolved-attribute": Error (Default),
|
||||
"unresolved-global": Warning (Default),
|
||||
"unresolved-import": Error (Default),
|
||||
"unresolved-reference": Error (Default),
|
||||
"unsupported-base": Warning (Default),
|
||||
"unsupported-bool-conversion": Error (Default),
|
||||
"unsupported-operator": Error (Default),
|
||||
"zero-stepsize-in-slice": Error (Default),
|
||||
},
|
||||
terminal: TerminalSettings {
|
||||
output_format: Full,
|
||||
error_on_warning: false,
|
||||
},
|
||||
src: SrcSettings {
|
||||
respect_ignore_files: true,
|
||||
files: IncludeExcludeFilter {
|
||||
include: IncludeFilter(
|
||||
[
|
||||
"**",
|
||||
],
|
||||
..
|
||||
),
|
||||
exclude: ExcludeFilter {
|
||||
ignore: Gitignore(
|
||||
[
|
||||
IgnoreGlob {
|
||||
original: "**/.bzr/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.direnv/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.eggs/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.git/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.git-rewrite/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.hg/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.mypy_cache/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.nox/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.pants.d/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.pytype/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.ruff_cache/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.svn/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.tox/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/.venv/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/__pypackages__/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/_build/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/buck-out/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/dist/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/node_modules/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
IgnoreGlob {
|
||||
original: "**/venv/",
|
||||
is_allow: false,
|
||||
is_only_dir: true,
|
||||
},
|
||||
],
|
||||
..
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
overrides: [],
|
||||
}
|
||||
|
||||
Memory report:
|
||||
=======SALSA STRUCTS=======
|
||||
`Program` metadata=[X.XXMB] fields=[X.XXMB] count=1
|
||||
`Project` metadata=[X.XXMB] fields=[X.XXMB] count=1
|
||||
`FileRoot` metadata=[X.XXMB] fields=[X.XXMB] count=1
|
||||
=======SALSA QUERIES=======
|
||||
=======SALSA SUMMARY=======
|
||||
TOTAL MEMORY USAGE: [X.XXMB]
|
||||
struct metadata = [X.XXMB]
|
||||
struct fields = [X.XXMB]
|
||||
memo metadata = [X.XXMB]
|
||||
memo fields = [X.XXMB]
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
source: crates/ty_server/tests/e2e/initialize.rs
|
||||
assertion_line: 17
|
||||
expression: initialization_result
|
||||
---
|
||||
{
|
||||
|
@ -32,6 +33,12 @@ expression: initialization_result
|
|||
"documentSymbolProvider": true,
|
||||
"workspaceSymbolProvider": true,
|
||||
"declarationProvider": true,
|
||||
"executeCommandProvider": {
|
||||
"commands": [
|
||||
"ty.printDebugInformation"
|
||||
],
|
||||
"workDoneProgress": false
|
||||
},
|
||||
"semanticTokensProvider": {
|
||||
"legend": {
|
||||
"tokenTypes": [
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
source: crates/ty_server/tests/e2e/initialize.rs
|
||||
assertion_line: 32
|
||||
expression: initialization_result
|
||||
---
|
||||
{
|
||||
|
@ -32,6 +33,12 @@ expression: initialization_result
|
|||
"documentSymbolProvider": true,
|
||||
"workspaceSymbolProvider": true,
|
||||
"declarationProvider": true,
|
||||
"executeCommandProvider": {
|
||||
"commands": [
|
||||
"ty.printDebugInformation"
|
||||
],
|
||||
"workDoneProgress": false
|
||||
},
|
||||
"semanticTokensProvider": {
|
||||
"legend": {
|
||||
"tokenTypes": [
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue