First pass getting user documentation in the IDE
This commit is contained in:
@@ -162,6 +162,17 @@ impl<'a> CompilationMetadata<'a> {
|
||||
name: Cow<'a, str>,
|
||||
parameters: Vec<Cow<'a, str>>,
|
||||
span: Option<Span>,
|
||||
) {
|
||||
self.add_function_with_doc(name, parameters, span, None);
|
||||
}
|
||||
|
||||
/// Adds a function symbol with optional doc comment.
|
||||
pub fn add_function_with_doc(
|
||||
&mut self,
|
||||
name: Cow<'a, str>,
|
||||
parameters: Vec<Cow<'a, str>>,
|
||||
span: Option<Span>,
|
||||
description: Option<Cow<'a, str>>,
|
||||
) {
|
||||
self.add_symbol(SymbolInfo {
|
||||
name,
|
||||
@@ -170,7 +181,7 @@ impl<'a> CompilationMetadata<'a> {
|
||||
return_type: None,
|
||||
},
|
||||
span,
|
||||
description: None,
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,6 +192,18 @@ impl<'a> CompilationMetadata<'a> {
|
||||
syscall_type: SyscallType,
|
||||
argument_count: usize,
|
||||
span: Option<Span>,
|
||||
) {
|
||||
self.add_syscall_with_doc(name, syscall_type, argument_count, span, None);
|
||||
}
|
||||
|
||||
/// Adds a syscall symbol with optional doc comment.
|
||||
pub fn add_syscall_with_doc(
|
||||
&mut self,
|
||||
name: Cow<'a, str>,
|
||||
syscall_type: SyscallType,
|
||||
argument_count: usize,
|
||||
span: Option<Span>,
|
||||
description: Option<Cow<'a, str>>,
|
||||
) {
|
||||
self.add_symbol(SymbolInfo {
|
||||
name,
|
||||
@@ -189,17 +212,27 @@ impl<'a> CompilationMetadata<'a> {
|
||||
argument_count,
|
||||
},
|
||||
span,
|
||||
description: None,
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds a variable symbol.
|
||||
pub fn add_variable(&mut self, name: Cow<'a, str>, span: Option<Span>) {
|
||||
self.add_variable_with_doc(name, span, None);
|
||||
}
|
||||
|
||||
/// Adds a variable symbol with optional doc comment.
|
||||
pub fn add_variable_with_doc(
|
||||
&mut self,
|
||||
name: Cow<'a, str>,
|
||||
span: Option<Span>,
|
||||
description: Option<Cow<'a, str>>,
|
||||
) {
|
||||
self.add_symbol(SymbolInfo {
|
||||
name,
|
||||
kind: SymbolKind::Variable { type_hint: None },
|
||||
span,
|
||||
description: None,
|
||||
description,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -276,16 +309,6 @@ mod tests {
|
||||
assert_eq!(variables.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // Requires complex Uri construction
|
||||
fn test_lsp_symbol_conversion() {
|
||||
let mut metadata = CompilationMetadata::new();
|
||||
metadata.add_function("test_func".into(), vec!["a".into(), "b".into()], None);
|
||||
|
||||
// In real usage with LSP, Uri would be passed from the server
|
||||
// This test demonstrates the conversion method exists and is type-safe
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lsp_completion_items() {
|
||||
let mut metadata = CompilationMetadata::new();
|
||||
|
||||
@@ -47,6 +47,15 @@ macro_rules! compile {
|
||||
output,
|
||||
}
|
||||
}};
|
||||
|
||||
(metadata $source:expr) => {{
|
||||
let compiler = crate::Compiler::new(
|
||||
parser::Parser::new(tokenizer::Tokenizer::from($source)),
|
||||
None,
|
||||
);
|
||||
let res = compiler.compile();
|
||||
res.metadata
|
||||
}};
|
||||
}
|
||||
mod binary_expression;
|
||||
mod branching;
|
||||
@@ -61,5 +70,6 @@ mod loops;
|
||||
mod math_syscall;
|
||||
mod negation_priority;
|
||||
mod scoping;
|
||||
mod symbol_documentation;
|
||||
mod syscall;
|
||||
mod tuple_literals;
|
||||
|
||||
120
rust_compiler/libs/compiler/src/test/symbol_documentation.rs
Normal file
120
rust_compiler/libs/compiler/src/test/symbol_documentation.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use anyhow::Result;
|
||||
|
||||
#[test]
|
||||
fn test_variable_doc_comment() -> Result<()> {
|
||||
let metadata = compile!(metadata "/// this is a documented variable\nlet myVar = 42;");
|
||||
|
||||
let var_symbol = metadata
|
||||
.symbols
|
||||
.iter()
|
||||
.find(|s| s.name == "myVar")
|
||||
.expect("myVar symbol not found");
|
||||
|
||||
assert_eq!(
|
||||
var_symbol.description.as_ref().map(|d| d.as_ref()),
|
||||
Some("this is a documented variable")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_const_doc_comment() -> Result<()> {
|
||||
let metadata = compile!(metadata "/// const documentation\nconst myConst = 100;");
|
||||
|
||||
let const_symbol = metadata
|
||||
.symbols
|
||||
.iter()
|
||||
.find(|s| s.name == "myConst")
|
||||
.expect("myConst symbol not found");
|
||||
|
||||
assert_eq!(
|
||||
const_symbol.description.as_ref().map(|d| d.as_ref()),
|
||||
Some("const documentation")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_device_doc_comment() -> Result<()> {
|
||||
let metadata = compile!(metadata "/// device documentation\ndevice myDevice = \"d0\";");
|
||||
|
||||
let device_symbol = metadata
|
||||
.symbols
|
||||
.iter()
|
||||
.find(|s| s.name == "myDevice")
|
||||
.expect("myDevice symbol not found");
|
||||
|
||||
assert_eq!(
|
||||
device_symbol.description.as_ref().map(|d| d.as_ref()),
|
||||
Some("device documentation")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_function_doc_comment() -> Result<()> {
|
||||
let metadata = compile!(metadata "/// function documentation\nfn test() { }");
|
||||
|
||||
let fn_symbol = metadata
|
||||
.symbols
|
||||
.iter()
|
||||
.find(|s| s.name == "test")
|
||||
.expect("test symbol not found");
|
||||
|
||||
assert_eq!(
|
||||
fn_symbol.description.as_ref().map(|d| d.as_ref()),
|
||||
Some("function documentation")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_syscall_documentation() -> Result<()> {
|
||||
let metadata = compile!(metadata "fn test() { clr(d0); }");
|
||||
|
||||
let clr_symbol = metadata
|
||||
.symbols
|
||||
.iter()
|
||||
.find(|s| s.name == "clr")
|
||||
.expect("clr syscall not found");
|
||||
|
||||
// clr should have its built-in documentation
|
||||
assert!(clr_symbol.description.is_some());
|
||||
assert!(!clr_symbol.description.as_ref().unwrap().is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_variable_references_have_tooltips() -> Result<()> {
|
||||
let metadata = compile!(metadata "/// documented variable\nlet myVar = 5;\nlet x = myVar + 2;\nmyVar = 10;");
|
||||
|
||||
// Count how many times 'myVar' appears in symbols
|
||||
let myvar_symbols: Vec<_> = metadata
|
||||
.symbols
|
||||
.iter()
|
||||
.filter(|s| s.name == "myVar")
|
||||
.collect();
|
||||
|
||||
// We should have at least 2: declaration + 1 reference (in myVar + 2)
|
||||
// The assignment `myVar = 10` is a write, not a read, so doesn't create a reference
|
||||
assert!(
|
||||
myvar_symbols.len() >= 2,
|
||||
"Expected at least 2 'myVar' symbols (declaration + reference), got {}",
|
||||
myvar_symbols.len()
|
||||
);
|
||||
|
||||
// All should have the same description
|
||||
let expected_desc = "documented variable";
|
||||
for sym in &myvar_symbols {
|
||||
assert_eq!(
|
||||
sym.description.as_ref().map(|d| d.as_ref()),
|
||||
Some(expected_desc),
|
||||
"Symbol description mismatch at {:?}",
|
||||
sym.span
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -465,10 +465,23 @@ impl<'a> Compiler<'a> {
|
||||
},
|
||||
Expression::Variable(name) => {
|
||||
match scope.get_location_of(&name.node, Some(name.span)) {
|
||||
Ok(loc) => Ok(Some(CompileLocation {
|
||||
location: loc,
|
||||
temp_name: None, // User variable, do not free
|
||||
})),
|
||||
Ok(loc) => {
|
||||
// Track this variable reference in metadata (for tooltips on all usages, not just declaration)
|
||||
let doc_comment: Option<Cow<'a, str>> = self
|
||||
.parser
|
||||
.get_declaration_doc(name.node.as_ref())
|
||||
.map(|s| Cow::Owned(s) as Cow<'a, str>);
|
||||
self.metadata.add_variable_with_doc(
|
||||
name.node.clone(),
|
||||
Some(name.span),
|
||||
doc_comment,
|
||||
);
|
||||
|
||||
Ok(Some(CompileLocation {
|
||||
location: loc,
|
||||
temp_name: None, // User variable, do not free
|
||||
}))
|
||||
}
|
||||
Err(_) => {
|
||||
// fallback, check devices
|
||||
if let Some(device) = self.devices.get(&name.node) {
|
||||
@@ -652,6 +665,14 @@ impl<'a> Compiler<'a> {
|
||||
if let Expression::Variable(ref name) = expr.node
|
||||
&& let Some(device_id) = self.devices.get(&name.node)
|
||||
{
|
||||
// Track this device reference in metadata (for tooltips on all usages, not just declaration)
|
||||
let doc_comment = self
|
||||
.parser
|
||||
.get_declaration_doc(name.node.as_ref())
|
||||
.map(Cow::Owned);
|
||||
self.metadata
|
||||
.add_variable_with_doc(name.node.clone(), Some(expr.span), doc_comment);
|
||||
|
||||
return Ok((Operand::Device(device_id.clone()), None));
|
||||
}
|
||||
|
||||
@@ -705,8 +726,12 @@ impl<'a> Compiler<'a> {
|
||||
let name_span = var_name.span;
|
||||
|
||||
// Track the variable in metadata
|
||||
let doc_comment = self
|
||||
.parser
|
||||
.get_declaration_doc(name_str.as_ref())
|
||||
.map(Cow::Owned);
|
||||
self.metadata
|
||||
.add_variable(name_str.clone(), Some(name_span));
|
||||
.add_variable_with_doc(name_str.clone(), Some(name_span), doc_comment);
|
||||
|
||||
// optimization. Check for a negated numeric literal (including nested negations)
|
||||
// e.g., -5, -(-5), -(-(5)), etc.
|
||||
@@ -1068,8 +1093,15 @@ impl<'a> Compiler<'a> {
|
||||
} = expr;
|
||||
|
||||
// Track the const variable in metadata
|
||||
self.metadata
|
||||
.add_variable(const_name.node.clone(), Some(const_name.span));
|
||||
let doc_comment = self
|
||||
.parser
|
||||
.get_declaration_doc(const_name.node.as_ref())
|
||||
.map(Cow::Owned);
|
||||
self.metadata.add_variable_with_doc(
|
||||
const_name.node.clone(),
|
||||
Some(const_name.span),
|
||||
doc_comment,
|
||||
);
|
||||
|
||||
// check for a hash expression or a literal
|
||||
let value = match const_value {
|
||||
@@ -1495,10 +1527,25 @@ impl<'a> Compiler<'a> {
|
||||
let TupleDeclarationExpression { names, value } = tuple_decl;
|
||||
|
||||
// Track each variable in the tuple declaration
|
||||
for name_spanned in &names {
|
||||
// Get doc for the first variable
|
||||
let first_var_name = names
|
||||
.iter()
|
||||
.find(|n| n.node.as_ref() != "_")
|
||||
.map(|n| n.node.to_string());
|
||||
let doc_comment = first_var_name
|
||||
.as_ref()
|
||||
.and_then(|name| self.parser.get_declaration_doc(name))
|
||||
.map(Cow::Owned);
|
||||
|
||||
for (i, name_spanned) in names.iter().enumerate() {
|
||||
if name_spanned.node.as_ref() != "_" {
|
||||
self.metadata
|
||||
.add_variable(name_spanned.node.clone(), Some(name_spanned.span));
|
||||
// Only attach doc comment to the first variable
|
||||
let comment = if i == 0 { doc_comment.clone() } else { None };
|
||||
self.metadata.add_variable_with_doc(
|
||||
name_spanned.node.clone(),
|
||||
Some(name_spanned.span),
|
||||
comment,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1941,8 +1988,15 @@ impl<'a> Compiler<'a> {
|
||||
expr: DeviceDeclarationExpression<'a>,
|
||||
) -> Result<(), Error<'a>> {
|
||||
// Track the device declaration in metadata
|
||||
self.metadata
|
||||
.add_variable(expr.name.node.clone(), Some(expr.name.span));
|
||||
let doc_comment = self
|
||||
.parser
|
||||
.get_declaration_doc(expr.name.node.as_ref())
|
||||
.map(Cow::Owned);
|
||||
self.metadata.add_variable_with_doc(
|
||||
expr.name.node.clone(),
|
||||
Some(expr.name.span),
|
||||
doc_comment,
|
||||
);
|
||||
|
||||
if self.devices.contains_key(&expr.name.node) {
|
||||
self.errors.push(Error::DuplicateIdentifier(
|
||||
@@ -2950,11 +3004,13 @@ impl<'a> Compiler<'a> {
|
||||
) -> Result<Option<CompileLocation<'a>>, Error<'a>> {
|
||||
// Track the syscall in metadata
|
||||
let syscall_name = expr.name();
|
||||
self.metadata.add_syscall(
|
||||
let doc = expr.docs().into();
|
||||
self.metadata.add_syscall_with_doc(
|
||||
Cow::Borrowed(syscall_name),
|
||||
crate::SyscallType::System,
|
||||
expr.arg_count(),
|
||||
Some(span),
|
||||
Some(doc),
|
||||
);
|
||||
|
||||
macro_rules! cleanup {
|
||||
@@ -3356,11 +3412,13 @@ impl<'a> Compiler<'a> {
|
||||
) -> Result<Option<CompileLocation<'a>>, Error<'a>> {
|
||||
// Track the syscall in metadata
|
||||
let syscall_name = expr.name();
|
||||
self.metadata.add_syscall(
|
||||
let doc = expr.docs().into();
|
||||
self.metadata.add_syscall_with_doc(
|
||||
Cow::Borrowed(syscall_name),
|
||||
crate::SyscallType::Math,
|
||||
expr.arg_count(),
|
||||
Some(span),
|
||||
Some(doc),
|
||||
);
|
||||
|
||||
macro_rules! cleanup {
|
||||
@@ -3625,8 +3683,16 @@ impl<'a> Compiler<'a> {
|
||||
|
||||
// Track the function definition in metadata
|
||||
let param_names: Vec<Cow<'a, str>> = arguments.iter().map(|a| a.node.clone()).collect();
|
||||
self.metadata
|
||||
.add_function(name.node.clone(), param_names, Some(name.span));
|
||||
let doc_comment = self
|
||||
.parser
|
||||
.get_declaration_doc(name.node.as_ref())
|
||||
.map(Cow::Owned);
|
||||
self.metadata.add_function_with_doc(
|
||||
name.node.clone(),
|
||||
param_names,
|
||||
Some(name.span),
|
||||
doc_comment,
|
||||
);
|
||||
|
||||
if self.function_meta.locations.contains_key(&name.node) {
|
||||
self.errors
|
||||
|
||||
Reference in New Issue
Block a user