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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion crates/plotnik-cli/src/commands/debug/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,21 @@ pub fn run(args: DebugArgs) {
None
};

let query = query_source.as_ref().map(|src| {
let mut query = query_source.as_ref().map(|src| {
Query::try_from(src).unwrap_or_else(|e| {
eprintln!("error: {}", e);
std::process::exit(1);
})
});

// Auto-link when --lang is provided with a query
if args.lang.is_some()
&& let Some(ref mut q) = query
{
let lang = resolve_lang_for_link(&args.lang);
q.link(&lang);
}

let show_query = has_query_input && !args.symbols;
let show_source = has_source_input;
let show_headers = (show_query || args.symbols) && show_source;
Expand Down Expand Up @@ -130,3 +138,11 @@ fn validate(args: &DebugArgs, has_query: bool, has_source: bool) -> Result<(), &

Ok(())
}

fn resolve_lang_for_link(lang: &Option<String>) -> plotnik_langs::Lang {
let name = lang.as_ref().expect("--lang required for --link");
plotnik_langs::from_name(name).unwrap_or_else(|| {
eprintln!("error: unknown language: {}", name);
std::process::exit(1);
})
}
44 changes: 44 additions & 0 deletions crates/plotnik-langs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ pub trait LangImpl: Send + Sync {
fn resolve_anonymous_node(&self, kind: &str) -> Option<NodeTypeId>;
fn resolve_field(&self, name: &str) -> Option<NodeFieldId>;

// Enumeration methods for suggestions
fn all_named_node_kinds(&self) -> Vec<&'static str>;
fn all_field_names(&self) -> Vec<&'static str>;
fn node_type_name(&self, node_type_id: NodeTypeId) -> Option<&'static str>;
fn field_name(&self, field_id: NodeFieldId) -> Option<&'static str>;
fn fields_for_node_type(&self, node_type_id: NodeTypeId) -> Vec<&'static str>;

fn is_supertype(&self, node_type_id: NodeTypeId) -> bool;
fn subtypes(&self, supertype: NodeTypeId) -> &[u16];

Expand Down Expand Up @@ -111,6 +118,43 @@ impl<N: NodeTypes + Send + Sync> LangImpl for LangInner<N> {
self.ts_lang.field_id_for_name(name)
}

fn all_named_node_kinds(&self) -> Vec<&'static str> {
let count = self.ts_lang.node_kind_count();
(0..count as u16)
.filter(|&id| self.ts_lang.node_kind_is_named(id))
.filter_map(|id| self.ts_lang.node_kind_for_id(id))
.collect()
}

fn all_field_names(&self) -> Vec<&'static str> {
let count = self.ts_lang.field_count();
(1..=count as u16)
.filter_map(|id| self.ts_lang.field_name_for_id(id))
.collect()
}

fn node_type_name(&self, node_type_id: NodeTypeId) -> Option<&'static str> {
self.ts_lang.node_kind_for_id(node_type_id)
}

fn field_name(&self, field_id: NodeFieldId) -> Option<&'static str> {
self.ts_lang.field_name_for_id(field_id.get())
}

fn fields_for_node_type(&self, node_type_id: NodeTypeId) -> Vec<&'static str> {
let count = self.ts_lang.field_count();
(1..=count as u16)
.filter_map(|id| {
let field_id = std::num::NonZeroU16::new(id)?;
if self.node_types.has_field(node_type_id, field_id) {
self.ts_lang.field_name_for_id(id)
} else {
None
}
})
.collect()
}

fn is_supertype(&self, node_type_id: NodeTypeId) -> bool {
self.ts_lang.node_kind_is_supertype(node_type_id)
}
Expand Down
4 changes: 4 additions & 0 deletions crates/plotnik-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ indexmap = "2"
rowan = "0.16.1"
serde = { version = "1.0.228", features = ["derive"] }
thiserror = "2.0.17"
plotnik-langs = { version = "0.1", path = "../plotnik-langs", optional = true }

[features]
default = ["plotnik-langs"]

[dev-dependencies]
insta = { version = "=1.44.3", features = ["yaml"] }
Expand Down
23 changes: 23 additions & 0 deletions crates/plotnik-lib/src/diagnostics/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ pub enum DiagnosticKind {
RecursionNoEscape,
FieldSequenceValue,

// Link pass - grammar validation
UnknownNodeType,
UnknownField,
FieldNotOnNodeType,
InvalidFieldChildType,

// Often consequences of earlier errors
UnnamedDefNotLast,
}
Expand Down Expand Up @@ -157,6 +163,12 @@ impl DiagnosticKind {
Self::RecursionNoEscape => "infinite recursion detected",
Self::FieldSequenceValue => "field must match exactly one node",

// Link pass - grammar validation
Self::UnknownNodeType => "unknown node type",
Self::UnknownField => "unknown field",
Self::FieldNotOnNodeType => "field not valid on this node type",
Self::InvalidFieldChildType => "node type not valid for this field",

// Structural
Self::UnnamedDefNotLast => "only the last definition can be unnamed",
}
Expand All @@ -177,6 +189,12 @@ impl DiagnosticKind {
Self::DuplicateDefinition => "`{}` is already defined".to_string(),
Self::UndefinedReference => "`{}` is not defined".to_string(),

// Link pass errors with context
Self::UnknownNodeType => "`{}` is not a valid node type".to_string(),
Self::UnknownField => "`{}` is not a valid field".to_string(),
Self::FieldNotOnNodeType => "field `{}` is not valid on this node type".to_string(),
Self::InvalidFieldChildType => "node type `{}` is not valid for this field".to_string(),

// Recursion with cycle path
Self::RecursionNoEscape => "infinite recursion: {}".to_string(),

Expand Down Expand Up @@ -272,6 +290,7 @@ pub(crate) struct DiagnosticMessage {
pub(crate) message: String,
pub(crate) fix: Option<Fix>,
pub(crate) related: Vec<RelatedInfo>,
pub(crate) hints: Vec<String>,
}

impl DiagnosticMessage {
Expand All @@ -283,6 +302,7 @@ impl DiagnosticMessage {
message: message.into(),
fix: None,
related: Vec::new(),
hints: Vec::new(),
}
}

Expand Down Expand Up @@ -325,6 +345,9 @@ impl std::fmt::Display for DiagnosticMessage {
u32::from(related.range.end())
)?;
}
for hint in &self.hints {
write!(f, " (hint: {})", hint)?;
}
Ok(())
}
}
5 changes: 5 additions & 0 deletions crates/plotnik-lib/src/diagnostics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ impl<'a> DiagnosticBuilder<'a> {
self
}

pub fn hint(mut self, hint: impl Into<String>) -> Self {
self.message.hints.push(hint.into());
self
}

pub fn emit(self) {
self.diagnostics.messages.push(self.message);
}
Expand Down
4 changes: 4 additions & 0 deletions crates/plotnik-lib/src/diagnostics/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ impl<'a> DiagnosticsPrinter<'a> {
);
}

for hint in &diag.hints {
report.push(Group::with_title(Level::HELP.secondary_title(hint)));
}

if i > 0 {
w.write_str("\n\n")?;
}
Expand Down
Loading