Skip to content
Merged
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
3 changes: 3 additions & 0 deletions crates/pgls_diagnostics/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ mod internal {
resource: match loc.resource {
Some(Resource::Argv) => Some(Resource::Argv),
Some(Resource::Memory) => Some(Resource::Memory),
Some(Resource::Database) => Some(Resource::Database),
Some(Resource::File(file)) => {
if let Some(Resource::File(path)) = &self.path {
Some(Resource::File(path.as_ref()))
Expand All @@ -468,6 +469,7 @@ mod internal {
None => self.path.as_ref().map(Resource::as_deref),
},
span: loc.span,
database_object: loc.database_object,
source_code: loc.source_code,
}
}
Expand Down Expand Up @@ -523,6 +525,7 @@ mod internal {
Location {
resource: loc.resource,
span: self.span.or(loc.span),
database_object: loc.database_object,
source_code: loc.source_code,
}
}
Expand Down
18 changes: 18 additions & 0 deletions crates/pgls_diagnostics/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@ impl<D: Diagnostic + ?Sized> fmt::Display for PrintHeader<'_, D> {
fmt.write_str(" ")?;
}

// Print the database object if present (e.g., "table public.contacts")
if let Some(db_obj) = location.database_object {
if let Some(obj_type) = db_obj.object_type {
fmt.write_markup(markup! {
<Dim>{obj_type}" "</Dim>
})?;
}
if let Some(schema) = db_obj.schema {
fmt.write_markup(markup! {
<Emphasis>{schema}"."</Emphasis>
})?;
}
fmt.write_markup(markup! {
<Emphasis>{db_obj.name}</Emphasis>" "
})?;
}

// Print the category of the diagnostic, with a hyperlink if
// the category has an associated link
if let Some(category) = diagnostic.category() {
Expand Down Expand Up @@ -790,6 +807,7 @@ mod tests {
visitor.record_frame(Location {
resource: Some(Resource::File("other_path")),
span: Some(TextRange::new(TextSize::from(8), TextSize::from(16))),
database_object: None,
source_code: Some(SourceCode {
text: "context location context",
line_starts: None,
Expand Down
4 changes: 3 additions & 1 deletion crates/pgls_diagnostics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ pub use crate::display::{
};
pub use crate::display_github::PrintGitHubDiagnostic;
pub use crate::error::{Error, Result};
pub use crate::location::{LineIndex, LineIndexBuf, Location, Resource, SourceCode};
pub use crate::location::{
DatabaseObject, DatabaseObjectOwned, LineIndex, LineIndexBuf, Location, Resource, SourceCode,
};
use pgls_console::fmt::{Formatter, Termcolor};
use pgls_console::markup;
use std::fmt::Write;
Expand Down
69 changes: 69 additions & 0 deletions crates/pgls_diagnostics/src/location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ use std::fmt::Debug;
use std::ops::Range;
use std::{borrow::Borrow, ops::Deref};

/// Represents a database object (table, function, etc.)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DatabaseObject<'a> {
/// Optional schema name
pub schema: Option<&'a str>,
/// Object name (required)
pub name: &'a str,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a thought: We'll probably refer to columns here as well? what will the name look like in that case, "table.column"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I would suggest we can add them when they are needed

/// Optional object type (e.g., "table", "function", "view")
pub object_type: Option<&'a str>,
}

/// Owned version of DatabaseObject for use in diagnostic structs
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DatabaseObjectOwned {
/// Optional schema name
pub schema: Option<String>,
/// Object name (required)
pub name: String,
/// Optional object type (e.g., "table", "function", "view")
pub object_type: Option<String>,
}

/// Represents the location of a diagnostic in a resource.
#[derive(Debug, Default, Clone, Copy)]
pub struct Location<'a> {
Expand All @@ -12,6 +34,8 @@ pub struct Location<'a> {
/// An optional range of text within the resource associated with the
/// diagnostic.
pub span: Option<TextRange>,
/// An optional database object reference
pub database_object: Option<DatabaseObject<'a>>,
/// The optional source code of the resource.
pub source_code: Option<BorrowedSourceCode<'a>>,
}
Expand All @@ -23,6 +47,7 @@ impl<'a> Location<'a> {
resource: None,
span: None,
source_code: None,
database_object: None,
}
}
}
Expand All @@ -42,6 +67,8 @@ impl Eq for Location<'_> {}
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
pub enum Resource<Path> {
/// The diagnostic is related to the database and its schema.
Database,
/// The diagnostic is related to the content of the command line arguments.
Argv,
/// The diagnostic is related to the content of a memory buffer.
Expand Down Expand Up @@ -70,6 +97,7 @@ impl<P> Resource<P> {
P: Deref,
{
match self {
Resource::Database => Resource::Database,
Resource::Argv => Resource::Argv,
Resource::Memory => Resource::Memory,
Resource::File(file) => Resource::File(file),
Expand All @@ -81,6 +109,7 @@ impl Resource<&'_ str> {
/// Converts a `Path<&str>` to `Path<String>`.
pub fn to_owned(self) -> Resource<String> {
match self {
Resource::Database => Resource::Database,
Resource::Argv => Resource::Argv,
Resource::Memory => Resource::Memory,
Resource::File(file) => Resource::File(file.to_owned()),
Expand Down Expand Up @@ -194,6 +223,7 @@ pub struct LocationBuilder<'a> {
resource: Option<Resource<&'a str>>,
span: Option<TextRange>,
source_code: Option<BorrowedSourceCode<'a>>,
database_object: Option<DatabaseObject<'a>>,
}

impl<'a> LocationBuilder<'a> {
Expand All @@ -212,11 +242,17 @@ impl<'a> LocationBuilder<'a> {
self
}

pub fn database_object<D: AsDatabaseObject>(mut self, database_object: &'a D) -> Self {
self.database_object = database_object.as_database_object();
self
}

pub fn build(self) -> Location<'a> {
Location {
resource: self.resource,
span: self.span,
source_code: self.source_code,
database_object: self.database_object,
}
}
}
Expand Down Expand Up @@ -342,6 +378,39 @@ impl AsSourceCode for String {
}
}

/// Utility trait for types that can be converted into a database object reference
pub trait AsDatabaseObject {
fn as_database_object(&self) -> Option<DatabaseObject<'_>>;
}

impl<T: AsDatabaseObject> AsDatabaseObject for Option<T> {
fn as_database_object(&self) -> Option<DatabaseObject<'_>> {
self.as_ref().and_then(T::as_database_object)
}
}

impl<T: AsDatabaseObject + ?Sized> AsDatabaseObject for &'_ T {
fn as_database_object(&self) -> Option<DatabaseObject<'_>> {
T::as_database_object(*self)
}
}

impl<'a> AsDatabaseObject for DatabaseObject<'a> {
fn as_database_object(&self) -> Option<DatabaseObject<'_>> {
Some(*self)
}
}

impl AsDatabaseObject for DatabaseObjectOwned {
fn as_database_object(&self) -> Option<DatabaseObject<'_>> {
Some(DatabaseObject {
schema: self.schema.as_deref(),
name: &self.name,
object_type: self.object_type.as_deref(),
})
}
}

#[cfg(test)]
mod tests {
use pgls_text_size::TextSize;
Expand Down
28 changes: 28 additions & 0 deletions crates/pgls_diagnostics/src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ impl super::Advices for Advice {
Advice::Frame(location) => visitor.record_frame(super::Location {
resource: location.path.as_ref().map(super::Resource::as_deref),
span: location.span,
database_object: None,
source_code: location.source_code.as_deref().map(|text| SourceCode {
text,
line_starts: None,
Expand Down Expand Up @@ -496,4 +497,31 @@ mod tests {
//
// assert_eq!(diag, expected);
// }

#[test]
fn test_database_object_location_macro() {
use crate::{DatabaseObjectOwned, Diagnostic};

#[derive(Debug, Diagnostic)]
#[diagnostic(severity = Error, category = "lint")]
struct TestDatabaseObjectDiagnostic {
#[location(database_object)]
db_object: DatabaseObjectOwned,
}

let diag = TestDatabaseObjectDiagnostic {
db_object: DatabaseObjectOwned {
schema: Some("public".to_string()),
name: "contacts".to_string(),
object_type: Some("table".to_string()),
},
};

let location = diag.location();
assert!(location.database_object.is_some());
let db_obj = location.database_object.unwrap();
assert_eq!(db_obj.schema, Some("public"));
assert_eq!(db_obj.name, "contacts");
assert_eq!(db_obj.object_type, Some("table"));
}
}
4 changes: 4 additions & 0 deletions crates/pgls_diagnostics_macros/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ pub(crate) enum LocationField {
Resource(Ident),
Span(Ident),
SourceCode(Ident),
DatabaseObject(Ident),
}

impl Parse for LocationAttr {
Expand All @@ -450,6 +451,8 @@ impl Parse for LocationAttr {
LocationField::Span(ident)
} else if ident == "source_code" {
LocationField::SourceCode(ident)
} else if ident == "database_object" {
LocationField::DatabaseObject(ident)
} else {
return Err(Error::new_spanned(ident, "unknown location field"));
};
Expand All @@ -467,6 +470,7 @@ impl ToTokens for LocationField {
LocationField::Resource(ident) => ident.to_tokens(tokens),
LocationField::Span(ident) => ident.to_tokens(tokens),
LocationField::SourceCode(ident) => ident.to_tokens(tokens),
LocationField::DatabaseObject(ident) => ident.to_tokens(tokens),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@ export type Advice =
/**
* Represents the resource a diagnostic is associated with.
*/
export type Resource_for_String = "argv" | "memory" | { file: string };
export type Resource_for_String =
| "database"
| "argv"
| "memory"
| { file: string };
export type TextRange = [TextSize, TextSize];
export interface MarkupNodeBuf {
content: string;
Expand Down
6 changes: 5 additions & 1 deletion packages/@postgrestools/backend-jsonrpc/src/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@ export type Advice =
/**
* Represents the resource a diagnostic is associated with.
*/
export type Resource_for_String = "argv" | "memory" | { file: string };
export type Resource_for_String =
| "database"
| "argv"
| "memory"
| { file: string };
export type TextRange = [TextSize, TextSize];
export interface MarkupNodeBuf {
content: string;
Expand Down