[red-knot] Add filename and source location for diagnostics (#12842)

## Summary

I'm not sure if this is useful but this is a hacky implementation to add
the filename and row / column numbers to the current Red Knot
diagnostics.
This commit is contained in:
Dhruv Manilawala 2024-08-12 21:26:30 +05:30 committed by GitHub
parent 540023262e
commit 99dc208b00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 74 additions and 12 deletions

2
Cargo.lock generated
View file

@ -1900,6 +1900,7 @@ dependencies = [
"ruff_python_ast", "ruff_python_ast",
"ruff_python_parser", "ruff_python_parser",
"ruff_python_stdlib", "ruff_python_stdlib",
"ruff_source_file",
"ruff_text_size", "ruff_text_size",
"rustc-hash 2.0.0", "rustc-hash 2.0.0",
"salsa", "salsa",
@ -1962,6 +1963,7 @@ dependencies = [
"ruff_cache", "ruff_cache",
"ruff_db", "ruff_db",
"ruff_python_ast", "ruff_python_ast",
"ruff_text_size",
"rustc-hash 2.0.0", "rustc-hash 2.0.0",
"salsa", "salsa",
"thiserror", "thiserror",

View file

@ -15,6 +15,7 @@ ruff_db = { workspace = true }
ruff_index = { workspace = true } ruff_index = { workspace = true }
ruff_python_ast = { workspace = true } ruff_python_ast = { workspace = true }
ruff_python_stdlib = { workspace = true } ruff_python_stdlib = { workspace = true }
ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true } ruff_text_size = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }

View file

@ -1,6 +1,8 @@
use ruff_db::files::File; use ruff_db::files::{File, FilePath};
use ruff_db::source::line_index;
use ruff_python_ast as ast; use ruff_python_ast as ast;
use ruff_python_ast::{Expr, ExpressionRef, StmtClassDef}; use ruff_python_ast::{Expr, ExpressionRef, StmtClassDef};
use ruff_source_file::LineIndex;
use crate::module_name::ModuleName; use crate::module_name::ModuleName;
use crate::module_resolver::{resolve_module, Module}; use crate::module_resolver::{resolve_module, Module};
@ -25,6 +27,14 @@ impl<'db> SemanticModel<'db> {
self.db self.db
} }
pub fn file_path(&self) -> &FilePath {
self.file.path(self.db)
}
pub fn line_index(&self) -> LineIndex {
line_index(self.db.upcast(), self.file)
}
pub fn resolve_module(&self, module_name: ModuleName) -> Option<Module> { pub fn resolve_module(&self, module_name: ModuleName) -> Option<Module> {
resolve_module(self.db, module_name) resolve_module(self.db, module_name)
} }

View file

@ -17,5 +17,5 @@ fn check() {
let result = workspace.check_file(&test).expect("Check to succeed"); let result = workspace.check_file(&test).expect("Check to succeed");
assert_eq!(result, vec!["Unresolved import 'random22'"]); assert_eq!(result, vec!["/test.py:1:8: Unresolved import 'random22'"]);
} }

View file

@ -17,6 +17,7 @@ red_knot_python_semantic = { workspace = true }
ruff_cache = { workspace = true } ruff_cache = { workspace = true }
ruff_db = { workspace = true, features = ["os", "cache"] } ruff_db = { workspace = true, features = ["os", "cache"] }
ruff_python_ast = { workspace = true } ruff_python_ast = { workspace = true }
ruff_text_size = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
crossbeam = { workspace = true } crossbeam = { workspace = true }

View file

@ -8,9 +8,10 @@ use red_knot_python_semantic::types::Type;
use red_knot_python_semantic::{HasTy, ModuleName, SemanticModel}; use red_knot_python_semantic::{HasTy, ModuleName, SemanticModel};
use ruff_db::files::File; use ruff_db::files::File;
use ruff_db::parsed::{parsed_module, ParsedModule}; use ruff_db::parsed::{parsed_module, ParsedModule};
use ruff_db::source::{source_text, SourceText}; use ruff_db::source::{line_index, source_text, SourceText};
use ruff_python_ast as ast; use ruff_python_ast as ast;
use ruff_python_ast::visitor::{walk_expr, walk_stmt, Visitor}; use ruff_python_ast::visitor::{walk_expr, walk_stmt, Visitor};
use ruff_text_size::{Ranged, TextSize};
use crate::db::Db; use crate::db::Db;
@ -49,7 +50,18 @@ pub(crate) fn lint_syntax(db: &dyn Db, file_id: File) -> Diagnostics {
visitor.visit_body(&ast.body); visitor.visit_body(&ast.body);
diagnostics = visitor.diagnostics; diagnostics = visitor.diagnostics;
} else { } else {
diagnostics.extend(parsed.errors().iter().map(ToString::to_string)); let path = file_id.path(db);
let line_index = line_index(db.upcast(), file_id);
diagnostics.extend(parsed.errors().iter().map(|err| {
let source_location = line_index.source_location(err.location.start(), source.as_str());
format!(
"{}:{}:{}: {}",
path.as_str(),
source_location.row,
source_location.column,
err,
)
}));
} }
Diagnostics::from(diagnostics) Diagnostics::from(diagnostics)
@ -97,6 +109,20 @@ pub fn lint_semantic(db: &dyn Db, file_id: File) -> Diagnostics {
Diagnostics::from(context.diagnostics.take()) Diagnostics::from(context.diagnostics.take())
} }
fn format_diagnostic(context: &SemanticLintContext, message: &str, start: TextSize) -> String {
let source_location = context
.semantic
.line_index()
.source_location(start, context.source_text());
format!(
"{}:{}:{}: {}",
context.semantic.file_path().as_str(),
source_location.row,
source_location.column,
message,
)
}
fn lint_unresolved_imports(context: &SemanticLintContext, import: AnyImportRef) { fn lint_unresolved_imports(context: &SemanticLintContext, import: AnyImportRef) {
match import { match import {
AnyImportRef::Import(import) => { AnyImportRef::Import(import) => {
@ -104,7 +130,11 @@ fn lint_unresolved_imports(context: &SemanticLintContext, import: AnyImportRef)
let ty = alias.ty(&context.semantic); let ty = alias.ty(&context.semantic);
if ty.is_unbound() { if ty.is_unbound() {
context.push_diagnostic(format!("Unresolved import '{}'", &alias.name)); context.push_diagnostic(format_diagnostic(
context,
&format!("Unresolved import '{}'", &alias.name),
alias.start(),
));
} }
} }
} }
@ -113,7 +143,11 @@ fn lint_unresolved_imports(context: &SemanticLintContext, import: AnyImportRef)
let ty = alias.ty(&context.semantic); let ty = alias.ty(&context.semantic);
if ty.is_unbound() { if ty.is_unbound() {
context.push_diagnostic(format!("Unresolved import '{}'", &alias.name)); context.push_diagnostic(format_diagnostic(
context,
&format!("Unresolved import '{}'", &alias.name),
alias.start(),
));
} }
} }
} }
@ -127,12 +161,17 @@ fn lint_maybe_undefined(context: &SemanticLintContext, name: &ast::ExprName) {
let semantic = &context.semantic; let semantic = &context.semantic;
match name.ty(semantic) { match name.ty(semantic) {
Type::Unbound => { Type::Unbound => {
context.push_diagnostic(format!("Name '{}' used when not defined.", &name.id)); context.push_diagnostic(format_diagnostic(
context,
&format!("Name '{}' used when not defined.", &name.id),
name.start(),
));
} }
Type::Union(union) if union.contains(semantic.db(), Type::Unbound) => { Type::Union(union) if union.contains(semantic.db(), Type::Unbound) => {
context.push_diagnostic(format!( context.push_diagnostic(format_diagnostic(
"Name '{}' used when possibly not defined.", context,
&name.id &format!("Name '{}' used when possibly not defined.", &name.id),
name.start(),
)); ));
} }
_ => {} _ => {}
@ -303,6 +342,15 @@ enum AnyImportRef<'a> {
ImportFrom(&'a ast::StmtImportFrom), ImportFrom(&'a ast::StmtImportFrom),
} }
impl Ranged for AnyImportRef<'_> {
fn range(&self) -> ruff_text_size::TextRange {
match self {
AnyImportRef::Import(import) => import.range(),
AnyImportRef::ImportFrom(import) => import.range(),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use red_knot_python_semantic::{Program, ProgramSettings, PythonVersion, SearchPathSettings}; use red_knot_python_semantic::{Program, ProgramSettings, PythonVersion, SearchPathSettings};
@ -364,8 +412,8 @@ mod tests {
assert_eq!( assert_eq!(
*messages, *messages,
vec![ vec![
"Name 'flag' used when not defined.", "/src/a.py:3:4: Name 'flag' used when not defined.",
"Name 'y' used when possibly not defined." "/src/a.py:5:1: Name 'y' used when possibly not defined."
] ]
); );
} }