From 1ef21739b752314c33fba5584f4c61b3c662095e Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <camiyoru@gmail.com> Date: Tue, 12 Mar 2024 03:02:56 +0800 Subject: [PATCH] feat: analyze lexical hierarchy for def-use relations --- crates/tinymist-query/src/analysis.rs | 33 ++ crates/tinymist-query/src/analysis/def_use.rs | 7 + .../src/analysis/lexical_hierarchy.rs | 420 +++++++++++++----- crates/tinymist-query/src/analysis/mod.rs | 10 - .../src/fixtures/document_symbols/func.typ | 1 + .../document_symbols/test@func.typ.snap | 13 + .../folding_range/test@paren_folding.typ.snap | 10 +- .../src/fixtures/lexical_hierarchy/base.typ | 3 + .../lexical_hierarchy/destructing.typ | 2 + .../src/fixtures/lexical_hierarchy/dict.typ | 5 + .../src/fixtures/lexical_hierarchy/func.typ | 2 + .../fixtures/lexical_hierarchy/redefine.typ | 2 + .../lexical_hierarchy/scope@base.typ.snap | 17 + .../scope@destructing.typ.snap | 37 ++ .../lexical_hierarchy/scope@dict.typ.snap | 27 ++ .../lexical_hierarchy/scope@func.typ.snap | 34 ++ .../lexical_hierarchy/scope@redefine.typ.snap | 22 + crates/tinymist-query/src/lib.rs | 16 +- 18 files changed, 534 insertions(+), 127 deletions(-) create mode 100644 crates/tinymist-query/src/analysis.rs create mode 100644 crates/tinymist-query/src/analysis/def_use.rs delete mode 100644 crates/tinymist-query/src/analysis/mod.rs create mode 100644 crates/tinymist-query/src/fixtures/document_symbols/func.typ create mode 100644 crates/tinymist-query/src/fixtures/document_symbols/test@func.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/lexical_hierarchy/base.typ create mode 100644 crates/tinymist-query/src/fixtures/lexical_hierarchy/destructing.typ create mode 100644 crates/tinymist-query/src/fixtures/lexical_hierarchy/dict.typ create mode 100644 crates/tinymist-query/src/fixtures/lexical_hierarchy/func.typ create mode 100644 crates/tinymist-query/src/fixtures/lexical_hierarchy/redefine.typ create mode 100644 crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@base.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@destructing.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@dict.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@func.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@redefine.typ.snap diff --git a/crates/tinymist-query/src/analysis.rs b/crates/tinymist-query/src/analysis.rs new file mode 100644 index 000000000..1df325660 --- /dev/null +++ b/crates/tinymist-query/src/analysis.rs @@ -0,0 +1,33 @@ +pub mod track_values; +pub use track_values::*; +pub mod lexical_hierarchy; +pub(crate) use lexical_hierarchy::*; +pub mod definition; +pub use definition::*; +pub mod import; +pub use import::*; +pub mod reference; +pub use reference::*; +pub mod def_use; +pub use def_use::*; + +#[cfg(test)] +mod lexical_hierarchy_tests { + use crate::analysis::lexical_hierarchy; + use crate::prelude::*; + use crate::tests::*; + + #[test] + fn scope() { + snapshot_testing("lexical_hierarchy", &|world, path| { + let source = get_suitable_source_in_workspace(world, &path).unwrap(); + + let result = lexical_hierarchy::get_lexical_hierarchy( + source, + lexical_hierarchy::LexicalScopeKind::DefUse, + ); + + assert_snapshot!(JsonRepr::new_redacted(result, &REDACT_LOC)); + }); + } +} diff --git a/crates/tinymist-query/src/analysis/def_use.rs b/crates/tinymist-query/src/analysis/def_use.rs new file mode 100644 index 000000000..2b1c0381a --- /dev/null +++ b/crates/tinymist-query/src/analysis/def_use.rs @@ -0,0 +1,7 @@ +use typst::syntax::Source; + +use super::{get_lexical_hierarchy, LexicalScopeKind}; + +pub fn get_def_use(source: Source) { + let _ = get_lexical_hierarchy(source, LexicalScopeKind::DefUse); +} diff --git a/crates/tinymist-query/src/analysis/lexical_hierarchy.rs b/crates/tinymist-query/src/analysis/lexical_hierarchy.rs index 42b66af05..d8f6c84b8 100644 --- a/crates/tinymist-query/src/analysis/lexical_hierarchy.rs +++ b/crates/tinymist-query/src/analysis/lexical_hierarchy.rs @@ -1,17 +1,53 @@ -use std::ops::Range; +use std::ops::{Deref, Range}; use anyhow::anyhow; use log::info; use lsp_types::SymbolKind; -use typst::syntax::{ast, LinkedNode, Source, SyntaxKind}; +use serde::{Deserialize, Serialize}; +use typst::{ + syntax::{ast, LinkedNode, Source, SyntaxKind}, + util::LazyHash, +}; use typst_ts_core::typst::prelude::{eco_vec, EcoVec}; -#[derive(Debug, Clone, Copy, Hash)] +pub(crate) fn get_lexical_hierarchy( + source: Source, + g: LexicalScopeKind, +) -> Option<EcoVec<LexicalHierarchy>> { + let b = std::time::Instant::now(); + let root = LinkedNode::new(source.root()); + + let mut worker = LexicalHierarchyWorker { + g, + ..LexicalHierarchyWorker::default() + }; + worker.stack.push(( + LexicalInfo { + name: "deadbeef".to_string(), + kind: LexicalKind::Namespace(-1), + range: 0..0, + }, + eco_vec![], + )); + let res = worker.get_symbols(root).ok(); + + while worker.stack.len() > 1 { + worker.symbreak(); + } + + let e = std::time::Instant::now(); + info!("lexical hierarchy analysis took {:?}", e - b); + res.map(|_| worker.stack.pop().unwrap().1) +} + +#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] pub(crate) enum LexicalKind { Namespace(i16), + ValRef, + LabelRef, Variable, Function, - Constant, + Label, Block, } @@ -23,8 +59,8 @@ impl TryFrom<LexicalKind> for SymbolKind { LexicalKind::Namespace(..) => Ok(SymbolKind::NAMESPACE), LexicalKind::Variable => Ok(SymbolKind::VARIABLE), LexicalKind::Function => Ok(SymbolKind::FUNCTION), - LexicalKind::Constant => Ok(SymbolKind::CONSTANT), - LexicalKind::Block => Err(()), + LexicalKind::Label => Ok(SymbolKind::CONSTANT), + LexicalKind::ValRef | LexicalKind::LabelRef | LexicalKind::Block => Err(()), } } } @@ -34,19 +70,32 @@ pub(crate) enum LexicalScopeKind { #[default] Symbol, Braced, + DefUse, } impl LexicalScopeKind { fn affect_symbol(&self) -> bool { - matches!(self, LexicalScopeKind::Symbol) + matches!(self, Self::DefUse | Self::Symbol) + } + + fn affect_ref(&self) -> bool { + matches!(self, Self::DefUse) + } + + fn affect_markup(&self) -> bool { + matches!(self, Self::Braced) } fn affect_block(&self) -> bool { - matches!(self, LexicalScopeKind::Braced) + matches!(self, Self::DefUse | Self::Braced) } fn affect_expr(&self) -> bool { - matches!(self, LexicalScopeKind::Braced) + matches!(self, Self::Braced) + } + + fn affect_heading(&self) -> bool { + matches!(self, Self::Symbol | Self::Braced) } } @@ -60,138 +109,301 @@ pub(crate) struct LexicalInfo { #[derive(Debug, Clone, Hash)] pub(crate) struct LexicalHierarchy { pub info: LexicalInfo, - pub children: Option<comemo::Prehashed<EcoVec<LexicalHierarchy>>>, + pub children: Option<LazyHash<EcoVec<LexicalHierarchy>>>, } -pub(crate) fn get_lexical_hierarchy( - source: Source, - g: LexicalScopeKind, -) -> Option<EcoVec<LexicalHierarchy>> { - fn symbreak(sym: LexicalInfo, curr: EcoVec<LexicalHierarchy>) -> LexicalHierarchy { - LexicalHierarchy { - info: sym, - children: if curr.is_empty() { - None - } else { - Some(comemo::Prehashed::new(curr)) - }, +impl Serialize for LexicalHierarchy { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + use serde::ser::SerializeStruct; + let mut state = serializer.serialize_struct("LexicalHierarchy", 2)?; + state.serialize_field("name", &self.info.name)?; + state.serialize_field("kind", &self.info.kind)?; + state.serialize_field("range", &self.info.range)?; + if let Some(children) = &self.children { + state.serialize_field("children", children.deref())?; } + state.end() } +} + +impl<'de> Deserialize<'de> for LexicalHierarchy { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + use serde::de::MapAccess; + struct LexicalHierarchyVisitor; + impl<'de> serde::de::Visitor<'de> for LexicalHierarchyVisitor { + type Value = LexicalHierarchy; - #[derive(Default)] - struct LexicalHierarchyWorker { - g: LexicalScopeKind, - stack: Vec<(LexicalInfo, EcoVec<LexicalHierarchy>)>, + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a struct") + } + + fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> { + let mut name = None; + let mut kind = None; + let mut range = None; + let mut children = None; + while let Some(key) = map.next_key()? { + match key { + "name" => name = Some(map.next_value()?), + "kind" => kind = Some(map.next_value()?), + "range" => range = Some(map.next_value()?), + "children" => children = Some(map.next_value()?), + _ => {} + } + } + let name = name.ok_or_else(|| serde::de::Error::missing_field("name"))?; + let kind = kind.ok_or_else(|| serde::de::Error::missing_field("kind"))?; + let range = range.ok_or_else(|| serde::de::Error::missing_field("range"))?; + Ok(LexicalHierarchy { + info: LexicalInfo { name, kind, range }, + children: children.map(LazyHash::new), + }) + } + } + + deserializer.deserialize_struct( + "LexicalHierarchy", + &["name", "kind", "range", "children"], + LexicalHierarchyVisitor, + ) } +} - impl LexicalHierarchyWorker { - fn symbreak(&mut self) { - let (symbol, children) = self.stack.pop().unwrap(); - let current = &mut self.stack.last_mut().unwrap().1; +#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq)] +enum IdentContext { + #[default] + Ref, + Func, + Var, + Params, +} + +#[derive(Default)] +struct LexicalHierarchyWorker { + g: LexicalScopeKind, + stack: Vec<(LexicalInfo, EcoVec<LexicalHierarchy>)>, + ident_context: IdentContext, +} + +impl LexicalHierarchyWorker { + fn symbreak(&mut self) { + let (symbol, children) = self.stack.pop().unwrap(); + let current = &mut self.stack.last_mut().unwrap().1; + + current.push(symbreak(symbol, children)); + } - current.push(symbreak(symbol, children)); + fn enter_symbol_context(&mut self, node: &LinkedNode) -> anyhow::Result<IdentContext> { + let checkpoint = self.ident_context; + match node.kind() { + SyntaxKind::RefMarker => self.ident_context = IdentContext::Ref, + SyntaxKind::LetBinding => self.ident_context = IdentContext::Ref, + SyntaxKind::Closure => self.ident_context = IdentContext::Func, + SyntaxKind::Params => self.ident_context = IdentContext::Params, + _ => {} } - /// Get all symbols for a node recursively. - fn get_symbols(&mut self, node: LinkedNode) -> anyhow::Result<()> { - let own_symbol = get_ident(&node, self.g)?; - - if let Some(symbol) = own_symbol { - if let LexicalKind::Namespace(level) = symbol.kind { - 'heading_break: while let Some((w, _)) = self.stack.last() { - match w.kind { - LexicalKind::Namespace(l) if l < level => break 'heading_break, - LexicalKind::Block => break 'heading_break, - _ if self.stack.len() <= 1 => break 'heading_break, - _ => {} - } + Ok(checkpoint) + } + + fn exit_symbol_context(&mut self, checkpoint: IdentContext) -> anyhow::Result<()> { + self.ident_context = checkpoint; + Ok(()) + } + + /// Get all symbols for a node recursively. + fn get_symbols(&mut self, node: LinkedNode) -> anyhow::Result<()> { + let own_symbol = self.get_ident(&node)?; - self.symbreak(); + let checkpoint = self.enter_symbol_context(&node)?; + + if let Some(symbol) = own_symbol { + if let LexicalKind::Namespace(level) = symbol.kind { + 'heading_break: while let Some((w, _)) = self.stack.last() { + match w.kind { + LexicalKind::Namespace(l) if l < level => break 'heading_break, + LexicalKind::Block => break 'heading_break, + _ if self.stack.len() <= 1 => break 'heading_break, + _ => {} } + + self.symbreak(); } - let is_heading = matches!(symbol.kind, LexicalKind::Namespace(..)); + } + let is_heading = matches!(symbol.kind, LexicalKind::Namespace(..)); + + self.stack.push((symbol, eco_vec![])); + let stack_height = self.stack.len(); - self.stack.push((symbol, eco_vec![])); - let stack_height = self.stack.len(); + for child in node.children() { + self.get_symbols(child)?; + } - for child in node.children() { - self.get_symbols(child)?; + if is_heading { + while stack_height < self.stack.len() { + self.symbreak(); + } + } else { + while stack_height <= self.stack.len() { + self.symbreak(); } + } + } else { + match node.kind() { + SyntaxKind::LetBinding => 'let_binding: { + let name = node.children().find(|n| n.cast::<ast::Pattern>().is_some()); + + if let Some(name) = &name { + let p = name.cast::<ast::Pattern>().unwrap(); - if is_heading { - while stack_height < self.stack.len() { - self.symbreak(); + // special case + if matches!(p, ast::Pattern::Normal(ast::Expr::Closure(..))) { + self.get_symbols_with(name.clone(), IdentContext::Ref)?; + break 'let_binding; + } } - } else { - while stack_height <= self.stack.len() { - self.symbreak(); + + // reverse order for correct symbol affection + if self.g == LexicalScopeKind::DefUse { + self.get_symbols_in_first_expr(node.children().rev())?; + if let Some(name) = name { + self.get_symbols_with(name, IdentContext::Var)?; + } + } else { + if let Some(name) = name { + self.get_symbols_with(name, IdentContext::Var)?; + } + self.get_symbols_in_first_expr(node.children().rev())?; } } - } else { - for child in node.children() { - self.get_symbols(child)?; + SyntaxKind::Closure => { + let n = node.children().next(); + if let Some(n) = n { + if n.kind() == SyntaxKind::Ident { + self.get_symbols_with(n, IdentContext::Func)?; + } + } + if self.g == LexicalScopeKind::DefUse { + let param = node.children().find(|n| n.kind() == SyntaxKind::Params); + if let Some(param) = param { + self.get_symbols_with(param, IdentContext::Params)?; + } + } + let body = node + .children() + .rev() + .find(|n| n.cast::<ast::Expr>().is_some()); + if let Some(body) = body { + if self.g == LexicalScopeKind::DefUse { + let symbol = LexicalInfo { + name: String::new(), + kind: LexicalKind::Block, + range: body.range(), + }; + self.stack.push((symbol, eco_vec![])); + let stack_height = self.stack.len(); + self.get_symbols_with(body, IdentContext::Ref)?; + while stack_height <= self.stack.len() { + self.symbreak(); + } + } else { + self.get_symbols_with(body, IdentContext::Ref)?; + } + } + } + SyntaxKind::FieldAccess => { + self.get_symbols_in_first_expr(node.children())?; + } + SyntaxKind::Named => { + if self.ident_context == IdentContext::Params { + let ident = node.children().find(|n| n.kind() == SyntaxKind::Ident); + if let Some(ident) = ident { + self.get_symbols_with(ident, IdentContext::Var)?; + } + } + + self.get_symbols_in_first_expr(node.children().rev())?; + } + _ => { + for child in node.children() { + self.get_symbols(child)?; + } } } + } + + self.exit_symbol_context(checkpoint)?; + + Ok(()) + } - Ok(()) + fn get_symbols_in_first_expr<'a>( + &mut self, + mut nodes: impl Iterator<Item = LinkedNode<'a>>, + ) -> anyhow::Result<()> { + let body = nodes.find(|n| n.cast::<ast::Expr>().is_some()); + if let Some(body) = body { + self.get_symbols_with(body, IdentContext::Ref)?; } + + Ok(()) + } + + fn get_symbols_with(&mut self, node: LinkedNode, context: IdentContext) -> anyhow::Result<()> { + let c = self.ident_context; + self.ident_context = context; + + let res = self.get_symbols(node); + + self.ident_context = c; + res } /// Get symbol for a leaf node of a valid type, or `None` if the node is an /// invalid type. #[allow(deprecated)] - fn get_ident(node: &LinkedNode, g: LexicalScopeKind) -> anyhow::Result<Option<LexicalInfo>> { + fn get_ident(&self, node: &LinkedNode) -> anyhow::Result<Option<LexicalInfo>> { let (name, kind) = match node.kind() { - SyntaxKind::Label if g.affect_symbol() => { + SyntaxKind::Label if self.g.affect_symbol() => { let ast_node = node .cast::<ast::Label>() .ok_or_else(|| anyhow!("cast to ast node failed: {:?}", node))?; let name = ast_node.get().to_string(); - (name, LexicalKind::Constant) + (name, LexicalKind::Label) + } + SyntaxKind::RefMarker if self.g.affect_ref() => { + let name = node.text().trim_start_matches('@').to_owned(); + (name, LexicalKind::LabelRef) } - SyntaxKind::Ident if g.affect_symbol() => { + SyntaxKind::Ident if self.g.affect_symbol() => { let ast_node = node .cast::<ast::Ident>() .ok_or_else(|| anyhow!("cast to ast node failed: {:?}", node))?; let name = ast_node.get().to_string(); - let Some(parent) = node.parent() else { - return Ok(None); - }; - let kind = match parent.kind() { - // for variable definitions, the Let binding holds an Ident - SyntaxKind::LetBinding => LexicalKind::Variable, - // for function definitions, the Let binding holds a Closure which holds the - // Ident - SyntaxKind::Closure => { - let Some(grand_parent) = parent.parent() else { - return Ok(None); - }; - match grand_parent.kind() { - SyntaxKind::LetBinding => LexicalKind::Function, - _ => return Ok(None), - } - } + let kind = match self.ident_context { + IdentContext::Ref if self.g.affect_ref() => LexicalKind::ValRef, + IdentContext::Func => LexicalKind::Function, + IdentContext::Var | IdentContext::Params => LexicalKind::Variable, _ => return Ok(None), }; (name, kind) } - SyntaxKind::Equation - | SyntaxKind::Raw - | SyntaxKind::CodeBlock - | SyntaxKind::ContentBlock - | SyntaxKind::BlockComment - if g.affect_block() => + SyntaxKind::Equation | SyntaxKind::Raw | SyntaxKind::BlockComment + if self.g.affect_markup() => { (String::new(), LexicalKind::Block) } + SyntaxKind::CodeBlock | SyntaxKind::ContentBlock if self.g.affect_block() => { + (String::new(), LexicalKind::Block) + } SyntaxKind::Parenthesized | SyntaxKind::Destructuring | SyntaxKind::Args | SyntaxKind::Array | SyntaxKind::Dict - if g.affect_expr() => + if self.g.affect_expr() => { (String::new(), LexicalKind::Block) } @@ -204,7 +416,7 @@ pub(crate) fn get_lexical_hierarchy( return Ok(None); }; let kind = match parent.kind() { - SyntaxKind::Heading => LexicalKind::Namespace( + SyntaxKind::Heading if self.g.affect_heading() => LexicalKind::Namespace( parent.cast::<ast::Heading>().unwrap().depth().get() as i16, ), _ => return Ok(None), @@ -221,29 +433,15 @@ pub(crate) fn get_lexical_hierarchy( range: node.range(), })) } +} - let b = std::time::Instant::now(); - let root = LinkedNode::new(source.root()); - - let mut worker = LexicalHierarchyWorker { - g, - ..LexicalHierarchyWorker::default() - }; - worker.stack.push(( - LexicalInfo { - name: "deadbeef".to_string(), - kind: LexicalKind::Namespace(-1), - range: 0..0, +fn symbreak(sym: LexicalInfo, curr: EcoVec<LexicalHierarchy>) -> LexicalHierarchy { + LexicalHierarchy { + info: sym, + children: if curr.is_empty() { + None + } else { + Some(LazyHash::new(curr)) }, - eco_vec![], - )); - let res = worker.get_symbols(root).ok(); - - while worker.stack.len() > 1 { - worker.symbreak(); } - - let e = std::time::Instant::now(); - info!("lexical hierarchy analysis took {:?}", e - b); - res.map(|_| worker.stack.pop().unwrap().1) } diff --git a/crates/tinymist-query/src/analysis/mod.rs b/crates/tinymist-query/src/analysis/mod.rs deleted file mode 100644 index 4bf52d6cd..000000000 --- a/crates/tinymist-query/src/analysis/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod track_values; -pub use track_values::*; -pub mod lexical_hierarchy; -pub(crate) use lexical_hierarchy::*; -pub mod definition; -pub use definition::*; -pub mod import; -pub use import::*; -pub mod reference; -pub use reference::*; diff --git a/crates/tinymist-query/src/fixtures/document_symbols/func.typ b/crates/tinymist-query/src/fixtures/document_symbols/func.typ new file mode 100644 index 000000000..47a753137 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/document_symbols/func.typ @@ -0,0 +1 @@ +#let f(a) = a; \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/document_symbols/test@func.typ.snap b/crates/tinymist-query/src/fixtures/document_symbols/test@func.typ.snap new file mode 100644 index 000000000..2f62641f8 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/document_symbols/test@func.typ.snap @@ -0,0 +1,13 @@ +--- +source: crates/tinymist-query/src/document_symbol.rs +expression: "JsonRepr::new_redacted(result.unwrap(), &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/document_symbols/func.typ +--- +[ + { + "kind": 12, + "name": "f", + "range": "0:5:0:6", + "selectionRange": "0:5:0:6" + } +] diff --git a/crates/tinymist-query/src/fixtures/folding_range/test@paren_folding.typ.snap b/crates/tinymist-query/src/fixtures/folding_range/test@paren_folding.typ.snap index df324b33a..80d1bd067 100644 --- a/crates/tinymist-query/src/fixtures/folding_range/test@paren_folding.typ.snap +++ b/crates/tinymist-query/src/fixtures/folding_range/test@paren_folding.typ.snap @@ -3,4 +3,12 @@ source: crates/tinymist-query/src/folding_range.rs expression: "JsonRepr::new_pure(result.unwrap())" input_file: crates/tinymist-query/src/fixtures/folding_range/paren_folding.typ --- -[] +[ + { + "collapsedText": "", + "endCharacter": 1, + "endLine": 2, + "startCharacter": 1, + "startLine": 0 + } +] diff --git a/crates/tinymist-query/src/fixtures/lexical_hierarchy/base.typ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/base.typ new file mode 100644 index 000000000..b93f9586b --- /dev/null +++ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/base.typ @@ -0,0 +1,3 @@ +// most simple def-use case +#let x = 1; +#x \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/lexical_hierarchy/destructing.typ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/destructing.typ new file mode 100644 index 000000000..f52a413a4 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/destructing.typ @@ -0,0 +1,2 @@ +#let (a, b) = (1, 1); +#let (a, b) = (b, a); \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/lexical_hierarchy/dict.typ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/dict.typ new file mode 100644 index 000000000..eda7ed9c6 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/dict.typ @@ -0,0 +1,5 @@ +#let z = 1; +#let x = ( + y: z, + "1 2": z, +) \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/lexical_hierarchy/func.typ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/func.typ new file mode 100644 index 000000000..a2140d57a --- /dev/null +++ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/func.typ @@ -0,0 +1,2 @@ +#let x = 1; +#let f(a) = a; \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/lexical_hierarchy/redefine.typ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/redefine.typ new file mode 100644 index 000000000..7e0744d35 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/redefine.typ @@ -0,0 +1,2 @@ +#let x = 1; +#let x = x; \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@base.typ.snap b/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@base.typ.snap new file mode 100644 index 000000000..42e2b0716 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@base.typ.snap @@ -0,0 +1,17 @@ +--- +source: crates/tinymist-query/src/analysis.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/lexical_hierarchy/base.typ +--- +[ + { + "kind": "Variable", + "name": "x", + "range": "5:6" + }, + { + "kind": "ValRef", + "name": "x", + "range": "14:15" + } +] diff --git a/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@destructing.typ.snap b/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@destructing.typ.snap new file mode 100644 index 000000000..5e142da41 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@destructing.typ.snap @@ -0,0 +1,37 @@ +--- +source: crates/tinymist-query/src/analysis.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/lexical_hierarchy/destructing.typ +--- +[ + { + "kind": "Variable", + "name": "a", + "range": "6:7" + }, + { + "kind": "Variable", + "name": "b", + "range": "9:10" + }, + { + "kind": "ValRef", + "name": "b", + "range": "38:39" + }, + { + "kind": "ValRef", + "name": "a", + "range": "41:42" + }, + { + "kind": "Variable", + "name": "a", + "range": "29:30" + }, + { + "kind": "Variable", + "name": "b", + "range": "32:33" + } +] diff --git a/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@dict.typ.snap b/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@dict.typ.snap new file mode 100644 index 000000000..af5aaed17 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@dict.typ.snap @@ -0,0 +1,27 @@ +--- +source: crates/tinymist-query/src/analysis.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/lexical_hierarchy/dict.typ +--- +[ + { + "kind": "Variable", + "name": "z", + "range": "5:6" + }, + { + "kind": "ValRef", + "name": "z", + "range": "30:31" + }, + { + "kind": "ValRef", + "name": "z", + "range": "43:44" + }, + { + "kind": "Variable", + "name": "x", + "range": "18:19" + } +] diff --git a/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@func.typ.snap b/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@func.typ.snap new file mode 100644 index 000000000..371e9070b --- /dev/null +++ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@func.typ.snap @@ -0,0 +1,34 @@ +--- +source: crates/tinymist-query/src/analysis.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/lexical_hierarchy/func.typ +--- +[ + { + "kind": "Variable", + "name": "x", + "range": "5:6" + }, + { + "kind": "Function", + "name": "f", + "range": "18:19" + }, + { + "kind": "Variable", + "name": "a", + "range": "20:21" + }, + { + "children": [ + { + "kind": "ValRef", + "name": "a", + "range": "25:26" + } + ], + "kind": "Block", + "name": "", + "range": "25:26" + } +] diff --git a/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@redefine.typ.snap b/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@redefine.typ.snap new file mode 100644 index 000000000..7bb763fbc --- /dev/null +++ b/crates/tinymist-query/src/fixtures/lexical_hierarchy/scope@redefine.typ.snap @@ -0,0 +1,22 @@ +--- +source: crates/tinymist-query/src/analysis.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/lexical_hierarchy/redefine.typ +--- +[ + { + "kind": "Variable", + "name": "x", + "range": "5:6" + }, + { + "kind": "ValRef", + "name": "x", + "range": "22:23" + }, + { + "kind": "Variable", + "name": "x", + "range": "18:19" + } +] diff --git a/crates/tinymist-query/src/lib.rs b/crates/tinymist-query/src/lib.rs index 022478bc3..0d64bb028 100644 --- a/crates/tinymist-query/src/lib.rs +++ b/crates/tinymist-query/src/lib.rs @@ -209,16 +209,18 @@ mod tests { for (i, source) in sources.enumerate() { // find prelude let mut source = source.trim(); - let path = if source.starts_with("//") { + let mut path = None; + + if source.starts_with("//") { let first_line = source.lines().next().unwrap(); source = source.strip_prefix(first_line).unwrap().trim(); let content = first_line.strip_prefix("//").unwrap().trim(); - content.strip_prefix("path:").unwrap().trim().to_owned() - } else { - format!("/source{i}.typ") + path = content.strip_prefix("path:").map(|e| e.trim().to_owned()) }; + let path = path.unwrap_or_else(|| format!("/source{i}.typ")); + let pw = root.join(Path::new(&path)); world .map_shadow(&pw, Bytes::from(source.as_bytes())) @@ -316,7 +318,11 @@ mod tests { } fn pos(v: &Value) -> String { - format!("{}:{}", v["line"], v["character"]) + match v { + Value::Object(v) => format!("{}:{}", v["line"], v["character"]), + Value::Number(v) => v.to_string(), + _ => "<null>".to_owned(), + } } impl Redact for RedactFields {