Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions crates/pyrefly_config/src/error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,15 @@ impl ErrorKind {
_ => Severity::Error,
}
}

/// Returns the public documentation URL for this error kind.
/// Example: https://pyrefly.org/en/docs/error-kinds/#bad-context-manager
pub fn docs_url(self) -> String {
format!(
"https://pyrefly.org/en/docs/error-kinds/#{}",
self.to_name()
)
}
}

#[cfg(test)]
Expand Down
14 changes: 11 additions & 3 deletions pyrefly/lib/error/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use std::io::Write;
use std::path::Path;

use itertools::Itertools;
use lsp_types::CodeDescription;
use lsp_types::Diagnostic;
use lsp_types::Url;
use pyrefly_python::module::Module;
use pyrefly_python::module_path::ModulePath;
use pyrefly_util::display::number_thousands;
Expand Down Expand Up @@ -168,6 +170,13 @@ impl Error {

/// Create a diagnostic suitable for use in LSP.
pub fn to_diagnostic(&self) -> Diagnostic {
let code = self.error_kind().to_name().to_owned();
let code_description = Url::parse(&format!(
"{}",
self.error_kind().docs_url()
))
.ok()
.map(|href| CodeDescription { href });
Diagnostic {
range: self.lined_buffer().to_lsp_range(self.range()),
severity: Some(match self.severity() {
Expand All @@ -179,9 +188,8 @@ impl Error {
}),
source: Some("Pyrefly".to_owned()),
message: self.msg().to_owned(),
code: Some(lsp_types::NumberOrString::String(
self.error_kind().to_name().to_owned(),
)),
code: Some(lsp_types::NumberOrString::String(code)),
code_description,
..Default::default()
}
}
Expand Down
83 changes: 83 additions & 0 deletions pyrefly/lib/test/lsp/lsp_interaction/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,86 @@ fn test_unexpected_keyword_range() {

interaction.shutdown();
}

#[test]
fn test_error_documentation_links() {
let test_files_root = get_test_files_root();
let mut interaction = LspInteraction::new();
interaction.set_root(test_files_root.path().to_path_buf());
interaction.initialize(InitializeSettings {
configuration: Some(None),
..Default::default()
});

interaction.server.did_change_configuration();

interaction.client.expect_configuration_request(2, None);
interaction.server.send_configuration_response(2, serde_json::json!([{"pyrefly": {"displayTypeErrors": "force-on"}}, {"pyrefly": {"displayTypeErrors": "force-on"}}]));

interaction.server.did_open("error_docs_test.py");
interaction.server.diagnostic("error_docs_test.py");

interaction.client.expect_response(Response {
id: RequestId::from(2),
result: Some(serde_json::json!({
"items": [
{
"code": "bad-assignment",
"codeDescription": {
"href": "https://pyrefly.org/en/docs/error-kinds/#bad-assignment"
},
"message": "`Literal['']` is not assignable to `int`",
"range": {
"end": {"character": 11, "line": 9},
"start": {"character": 9, "line": 9}
},
"severity": 1,
"source": "Pyrefly"
},
{
"code": "bad-context-manager",
"codeDescription": {
"href": "https://pyrefly.org/en/docs/error-kinds/#bad-context-manager"
},
"message": "Cannot use `A` as a context manager\n Object of class `A` has no attribute `__enter__`",
"range": {
"end": {"character": 8, "line": 15},
"start": {"character": 5, "line": 15}
},
"severity": 1,
"source": "Pyrefly"
},
{
"code": "bad-context-manager",
"codeDescription": {
"href": "https://pyrefly.org/en/docs/error-kinds/#bad-context-manager"
},
"message": "Cannot use `A` as a context manager\n Object of class `A` has no attribute `__exit__`",
"range": {
"end": {"character": 8, "line": 15},
"start": {"character": 5, "line": 15}
},
"severity": 1,
"source": "Pyrefly"
},
{
"code": "missing-attribute",
"codeDescription": {
"href": "https://pyrefly.org/en/docs/error-kinds/#missing-attribute"
},
"message": "Object of class `object` has no attribute `nonexistent_method`",
"range": {
"end": {"character": 22, "line": 20},
"start": {"character": 0, "line": 20}
},
"severity": 1,
"source": "Pyrefly"
}
],
"kind": "full"
})),
error: None,
});

interaction.shutdown();
}
21 changes: 21 additions & 0 deletions pyrefly/lib/test/lsp/lsp_interaction/test_files/error_docs_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

# Test file for error documentation links
# This file contains various error types to test that documentation links are properly included

# Bad assignment error
x: int = ""

# Bad context manager error
class A:
pass

with A():
pass

# Missing attribute error
obj = object()
obj.nonexistent_method()