mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
feat: analyze lexical hierarchy for def-use relations (#17)
This commit is contained in:
parent
f8194c76b0
commit
ee131ac68a
18 changed files with 637 additions and 230 deletions
33
crates/tinymist-query/src/analysis.rs
Normal file
33
crates/tinymist-query/src/analysis.rs
Normal file
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
7
crates/tinymist-query/src/analysis/def_use.rs
Normal file
7
crates/tinymist-query/src/analysis/def_use.rs
Normal file
|
@ -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);
|
||||
}
|
|
@ -1,227 +1,19 @@
|
|||
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) enum LexicalKind {
|
||||
Namespace(i16),
|
||||
Variable,
|
||||
Function,
|
||||
Constant,
|
||||
Block,
|
||||
}
|
||||
|
||||
impl TryFrom<LexicalKind> for SymbolKind {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: LexicalKind) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
LexicalKind::Namespace(..) => Ok(SymbolKind::NAMESPACE),
|
||||
LexicalKind::Variable => Ok(SymbolKind::VARIABLE),
|
||||
LexicalKind::Function => Ok(SymbolKind::FUNCTION),
|
||||
LexicalKind::Constant => Ok(SymbolKind::CONSTANT),
|
||||
LexicalKind::Block => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq)]
|
||||
pub(crate) enum LexicalScopeKind {
|
||||
#[default]
|
||||
Symbol,
|
||||
Braced,
|
||||
}
|
||||
|
||||
impl LexicalScopeKind {
|
||||
fn affect_symbol(&self) -> bool {
|
||||
matches!(self, LexicalScopeKind::Symbol)
|
||||
}
|
||||
|
||||
fn affect_block(&self) -> bool {
|
||||
matches!(self, LexicalScopeKind::Braced)
|
||||
}
|
||||
|
||||
fn affect_expr(&self) -> bool {
|
||||
matches!(self, LexicalScopeKind::Braced)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub(crate) struct LexicalInfo {
|
||||
pub name: String,
|
||||
pub kind: LexicalKind,
|
||||
pub range: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub(crate) struct LexicalHierarchy {
|
||||
pub info: LexicalInfo,
|
||||
pub children: Option<comemo::Prehashed<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))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct LexicalHierarchyWorker {
|
||||
g: LexicalScopeKind,
|
||||
stack: Vec<(LexicalInfo, EcoVec<LexicalHierarchy>)>,
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/// 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,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.symbreak();
|
||||
}
|
||||
}
|
||||
let is_heading = matches!(symbol.kind, LexicalKind::Namespace(..));
|
||||
|
||||
self.stack.push((symbol, eco_vec![]));
|
||||
let stack_height = self.stack.len();
|
||||
|
||||
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 {
|
||||
for child in node.children() {
|
||||
self.get_symbols(child)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
let (name, kind) = match node.kind() {
|
||||
SyntaxKind::Label if 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)
|
||||
}
|
||||
SyntaxKind::Ident if 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),
|
||||
}
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
(name, kind)
|
||||
}
|
||||
SyntaxKind::Equation
|
||||
| SyntaxKind::Raw
|
||||
| SyntaxKind::CodeBlock
|
||||
| SyntaxKind::ContentBlock
|
||||
| SyntaxKind::BlockComment
|
||||
if g.affect_block() =>
|
||||
{
|
||||
(String::new(), LexicalKind::Block)
|
||||
}
|
||||
SyntaxKind::Parenthesized
|
||||
| SyntaxKind::Destructuring
|
||||
| SyntaxKind::Args
|
||||
| SyntaxKind::Array
|
||||
| SyntaxKind::Dict
|
||||
if g.affect_expr() =>
|
||||
{
|
||||
(String::new(), LexicalKind::Block)
|
||||
}
|
||||
SyntaxKind::Markup => {
|
||||
let name = node.get().to_owned().into_text().to_string();
|
||||
if name.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(parent) = node.parent() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let kind = match parent.kind() {
|
||||
SyntaxKind::Heading => LexicalKind::Namespace(
|
||||
parent.cast::<ast::Heading>().unwrap().depth().get() as i16,
|
||||
),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
(name, kind)
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
Ok(Some(LexicalInfo {
|
||||
name,
|
||||
kind,
|
||||
range: node.range(),
|
||||
}))
|
||||
}
|
||||
|
||||
let b = std::time::Instant::now();
|
||||
let root = LinkedNode::new(source.root());
|
||||
|
||||
|
@ -247,3 +39,409 @@ pub(crate) fn get_lexical_hierarchy(
|
|||
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,
|
||||
Label,
|
||||
Block,
|
||||
}
|
||||
|
||||
impl TryFrom<LexicalKind> for SymbolKind {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: LexicalKind) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
LexicalKind::Namespace(..) => Ok(SymbolKind::NAMESPACE),
|
||||
LexicalKind::Variable => Ok(SymbolKind::VARIABLE),
|
||||
LexicalKind::Function => Ok(SymbolKind::FUNCTION),
|
||||
LexicalKind::Label => Ok(SymbolKind::CONSTANT),
|
||||
LexicalKind::ValRef | LexicalKind::LabelRef | LexicalKind::Block => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, Default, PartialEq, Eq)]
|
||||
pub(crate) enum LexicalScopeKind {
|
||||
#[default]
|
||||
Symbol,
|
||||
Braced,
|
||||
DefUse,
|
||||
}
|
||||
|
||||
impl LexicalScopeKind {
|
||||
fn affect_symbol(&self) -> bool {
|
||||
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, Self::DefUse | Self::Braced)
|
||||
}
|
||||
|
||||
fn affect_expr(&self) -> bool {
|
||||
matches!(self, Self::Braced)
|
||||
}
|
||||
|
||||
fn affect_heading(&self) -> bool {
|
||||
matches!(self, Self::Symbol | Self::Braced)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub(crate) struct LexicalInfo {
|
||||
pub name: String,
|
||||
pub kind: LexicalKind,
|
||||
pub range: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub(crate) struct LexicalHierarchy {
|
||||
pub info: LexicalInfo,
|
||||
pub children: Option<LazyHash<EcoVec<LexicalHierarchy>>>,
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
||||
|
||||
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,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
||||
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(..));
|
||||
|
||||
self.stack.push((symbol, eco_vec![]));
|
||||
let stack_height = self.stack.len();
|
||||
|
||||
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();
|
||||
|
||||
// special case
|
||||
if matches!(p, ast::Pattern::Normal(ast::Expr::Closure(..))) {
|
||||
self.get_symbols_with(name.clone(), IdentContext::Ref)?;
|
||||
break 'let_binding;
|
||||
}
|
||||
}
|
||||
|
||||
// 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())?;
|
||||
}
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
||||
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(&self, node: &LinkedNode) -> anyhow::Result<Option<LexicalInfo>> {
|
||||
let (name, kind) = match node.kind() {
|
||||
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::Label)
|
||||
}
|
||||
SyntaxKind::RefMarker if self.g.affect_ref() => {
|
||||
let name = node.text().trim_start_matches('@').to_owned();
|
||||
(name, LexicalKind::LabelRef)
|
||||
}
|
||||
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 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::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 self.g.affect_expr() =>
|
||||
{
|
||||
(String::new(), LexicalKind::Block)
|
||||
}
|
||||
SyntaxKind::Markup => {
|
||||
let name = node.get().to_owned().into_text().to_string();
|
||||
if name.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let Some(parent) = node.parent() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let kind = match parent.kind() {
|
||||
SyntaxKind::Heading if self.g.affect_heading() => LexicalKind::Namespace(
|
||||
parent.cast::<ast::Heading>().unwrap().depth().get() as i16,
|
||||
),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
(name, kind)
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
Ok(Some(LexicalInfo {
|
||||
name,
|
||||
kind,
|
||||
range: node.range(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn symbreak(sym: LexicalInfo, curr: EcoVec<LexicalHierarchy>) -> LexicalHierarchy {
|
||||
LexicalHierarchy {
|
||||
info: sym,
|
||||
children: if curr.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(LazyHash::new(curr))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::*;
|
|
@ -0,0 +1 @@
|
|||
#let f(a) = a;
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
// most simple def-use case
|
||||
#let x = 1;
|
||||
#x
|
|
@ -0,0 +1,2 @@
|
|||
#let (a, b) = (1, 1);
|
||||
#let (a, b) = (b, a);
|
|
@ -0,0 +1,5 @@
|
|||
#let z = 1;
|
||||
#let x = (
|
||||
y: z,
|
||||
"1 2": z,
|
||||
)
|
|
@ -0,0 +1,2 @@
|
|||
#let x = 1;
|
||||
#let f(a) = a;
|
|
@ -0,0 +1,2 @@
|
|||
#let x = 1;
|
||||
#let x = x;
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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"
|
||||
}
|
||||
]
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue