From 1295c8754af51dbdba78a3ced4e387dabf355b6f Mon Sep 17 00:00:00 2001 From: Yifan Song <41675306+Eric-Song-Nop@users.noreply.github.com> Date: Wed, 14 Aug 2024 03:21:44 +0200 Subject: [PATCH] feat: find references for `Ref` and `Label` (#527) * add test for `goto_definition` with label reference * simplify test for `goto_definition` with reference * abstract compile doc for test * add snap for goto_definition label reference * basic goto_reference with simple test * basic find_reference for Ref * fix: a bug in linked def * Remove unwanted compile directive compile: true * simply compile and get depended files fail for corss_file_ref_label * update ref reference but still fail to get source from path * fix: reuse find definition effort and handle undefined references for labels * dev: update test case * fix: label reference analysis * fix: rust analyzer doing bad * dev: update snapshot * dev: lift common pattern * fix: unstable snapshot --------- Co-authored-by: Myriad-Dreamin --- crates/tinymist-query/src/analysis/def_use.rs | 51 +++-- crates/tinymist-query/src/analysis/global.rs | 14 +- .../tinymist-query/src/analysis/linked_def.rs | 33 ++- crates/tinymist-query/src/completion.rs | 7 +- .../references/cross_file_ref_label.typ | 19 ++ .../src/fixtures/references/label.typ | 6 + .../src/fixtures/references/ref_label.typ | 10 + .../snaps/test@cross_file_ref_label.typ.snap | 11 + .../references/snaps/test@label.typ.snap | 8 + .../snaps/test@recursive_import.typ.snap | 2 +- .../references/snaps/test@ref_label.typ.snap | 10 + crates/tinymist-query/src/goto_definition.rs | 9 +- crates/tinymist-query/src/references.rs | 198 +++++++++--------- crates/tinymist-query/src/rename.rs | 3 +- crates/tinymist-query/src/tests.rs | 33 ++- crates/tinymist/src/server.rs | 2 +- tests/e2e/main.rs | 2 +- 17 files changed, 249 insertions(+), 169 deletions(-) create mode 100644 crates/tinymist-query/src/fixtures/references/cross_file_ref_label.typ create mode 100644 crates/tinymist-query/src/fixtures/references/label.typ create mode 100644 crates/tinymist-query/src/fixtures/references/ref_label.typ create mode 100644 crates/tinymist-query/src/fixtures/references/snaps/test@cross_file_ref_label.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/references/snaps/test@label.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/references/snaps/test@ref_label.typ.snap diff --git a/crates/tinymist-query/src/analysis/def_use.rs b/crates/tinymist-query/src/analysis/def_use.rs index 8061d4f4..53ef507c 100644 --- a/crates/tinymist-query/src/analysis/def_use.rs +++ b/crates/tinymist-query/src/analysis/def_use.rs @@ -28,6 +28,8 @@ pub struct DefUseInfo { external_refs: ExternalRefMap, /// The references to defined symbols. pub ident_refs: HashMap, + /// The references of labels. + pub label_refs: HashMap>>, /// The references to undefined symbols. pub undefined_refs: Vec, exports_refs: Vec, @@ -56,8 +58,9 @@ impl DefUseInfo { + 32) + self.ident_refs.capacity() * (std::mem::size_of::() + std::mem::size_of::() + 32) - + (self.undefined_refs.capacity() * std::mem::size_of::() + 32) - + (self.exports_refs.capacity() * std::mem::size_of::() + 32) + + self.label_refs.capacity() * (std::mem::size_of::>() + 32) + + self.undefined_refs.capacity() * (std::mem::size_of::() + 32) + + self.exports_refs.capacity() * (std::mem::size_of::() + 32) + self.exports_defs.capacity() * (std::mem::size_of::() + std::mem::size_of::() + 32) } @@ -226,12 +229,12 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> { LexicalKind::Var(LexicalVarKind::Label) => { self.insert(Ns::Label, e); } - LexicalKind::Var(LexicalVarKind::LabelRef) => self.insert_ref(Ns::Label, e), + LexicalKind::Var(LexicalVarKind::LabelRef) => self.insert_label_ref(e), LexicalKind::Var(LexicalVarKind::Function) | LexicalKind::Var(LexicalVarKind::Variable) => { self.insert(Ns::Value, e); } - LexicalKind::Var(LexicalVarKind::ValRef) => self.insert_ref(Ns::Value, e), + LexicalKind::Var(LexicalVarKind::ValRef) => self.insert_value_ref(e), LexicalKind::Block => { if let Some(e) = &e.children { self.enter(|this| this.scan(e.as_slice()))?; @@ -266,7 +269,7 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> { | LexicalKind::Mod(LexicalModKind::ModuleAlias) => self.insert_module(Ns::Value, e), LexicalKind::Mod(LexicalModKind::Ident) => match self.import_name(&e.info.name) { Some(()) => { - self.insert_ref(Ns::Value, e); + self.insert_value_ref(e); } None => { let def_id = self.insert(Ns::Value, e); @@ -276,13 +279,10 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> { LexicalKind::Mod(LexicalModKind::Alias { target }) => { match self.import_name(&target.name) { Some(()) => { - self.insert_ident_ref( - Ns::Value, - IdentRef { - name: target.name.clone(), - range: target.range.clone(), - }, - ); + self.insert_value_ref_(IdentRef { + name: target.name.clone(), + range: target.range.clone(), + }); self.insert(Ns::Value, e); } None => { @@ -350,13 +350,8 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> { id } - fn insert_ident_ref(&mut self, label: Ns, id_ref: IdentRef) { - let snap = match label { - Ns::Label => &mut self.label_scope, - Ns::Value => &mut self.id_scope, - }; - - match snap.get(&id_ref.name) { + fn insert_value_ref_(&mut self, id_ref: IdentRef) { + match self.id_scope.get(&id_ref.name) { Some(id) => { self.info.ident_refs.insert(id_ref, *id); } @@ -366,13 +361,15 @@ impl<'a, 'b, 'w> DefUseCollector<'a, 'b, 'w> { } } - fn insert_ref(&mut self, label: Ns, e: &LexicalHierarchy) { - self.insert_ident_ref( - label, - IdentRef { - name: e.info.name.clone(), - range: e.info.range.clone(), - }, - ); + fn insert_value_ref(&mut self, e: &LexicalHierarchy) { + self.insert_value_ref_(IdentRef { + name: e.info.name.clone(), + range: e.info.range.clone(), + }); + } + + fn insert_label_ref(&mut self, e: &LexicalHierarchy) { + let refs = self.info.label_refs.entry(e.info.name.clone()).or_default(); + refs.push(e.info.range.clone()); } } diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index b6f57129..c4eeec2b 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -14,7 +14,7 @@ use reflexo::hash::{hash128, FxDashMap}; use reflexo::{debug_loc::DataSource, ImmutPath}; use typst::eval::Eval; use typst::foundations::{self, Func}; -use typst::syntax::{LinkedNode, SyntaxNode}; +use typst::syntax::{FileId, LinkedNode, SyntaxNode}; use typst::{ diag::{eco_format, FileError, FileResult, PackageError}, foundations::Bytes, @@ -310,8 +310,8 @@ impl<'w> AnalysisContext<'w> { self.get(id).unwrap().source(self, id) } - /// Get the source of a file by file path. - pub fn source_by_path(&mut self, p: &Path) -> FileResult { + /// Get the fileId from its path + pub fn file_id_by_path(&self, p: &Path) -> FileResult { // todo: source in packages let relative_path = p.strip_prefix(&self.root).map_err(|_| { FileError::Other(Some(eco_format!( @@ -320,7 +320,13 @@ impl<'w> AnalysisContext<'w> { ))) })?; - let id = TypstFileId::new(None, VirtualPath::new(relative_path)); + Ok(TypstFileId::new(None, VirtualPath::new(relative_path))) + } + + /// Get the source of a file by file path. + pub fn source_by_path(&mut self, p: &Path) -> FileResult { + // todo: source in packages + let id = self.file_id_by_path(p)?; self.source_by_id(id) } diff --git a/crates/tinymist-query/src/analysis/linked_def.rs b/crates/tinymist-query/src/analysis/linked_def.rs index 284ed061..55f7d548 100644 --- a/crates/tinymist-query/src/analysis/linked_def.rs +++ b/crates/tinymist-query/src/analysis/linked_def.rs @@ -74,9 +74,9 @@ pub fn find_definition( } DerefTarget::Label(r) | DerefTarget::Ref(r) => { let ref_expr: ast::Expr = r.cast()?; - let ref_node = match ref_expr { - ast::Expr::Ref(r) => r.target(), - ast::Expr::Label(r) => r.get(), + let (ref_node, is_label) = match ref_expr { + ast::Expr::Ref(r) => (r.target(), false), + ast::Expr::Label(r) => (r.get(), true), _ => return None, }; @@ -100,19 +100,34 @@ pub fn find_definition( .or_else(|| { let sel = Selector::Label(label); let elem = introspector.query_first(&sel)?; - let span = elem.span(); - let fid = span.id()?; - let source = ctx.source_by_id(fid).ok()?; + // if it is a label, we put the selection range to itself + let (def_at, name_range) = if is_label { + let span = r.span(); + let fid = span.id()?; + let source = ctx.source_by_id(fid).ok()?; + let rng = source.range(span)?; - let rng = source.range(span)?; + let name_range = rng.start + 1..rng.end - 1; + let name_range = (name_range.start <= name_range.end).then_some(name_range); + (Some((fid, rng)), name_range) + } else { + // otherwise, it is estimated to the span of the pointed content + // todo: get the label's span + let span = elem.span(); + let fid = span.id()?; + let source = ctx.source_by_id(fid).ok()?; + let rng = source.range(span)?; + + (Some((fid, rng)), None) + }; Some(DefinitionLink { kind: LexicalKind::Var(LexicalVarKind::Label), name: ref_node.to_owned(), value: Some(Value::Content(elem)), - def_at: Some((fid, rng.clone())), - name_range: Some(rng.clone()), + def_at, + name_range, }) }); } diff --git a/crates/tinymist-query/src/completion.rs b/crates/tinymist-query/src/completion.rs index b460864d..fd204aee 100644 --- a/crates/tinymist-query/src/completion.rs +++ b/crates/tinymist-query/src/completion.rs @@ -320,12 +320,7 @@ mod tests { let mut includes = HashSet::new(); let mut excludes = HashSet::new(); - let must_compile = has_test_property(&properties, "compile"); - let doc = if must_compile { - compile_doc_for_test(ctx) - } else { - None - }; + let doc = compile_doc_for_test(ctx, &properties); for kk in properties.get("contains").iter().flat_map(|v| v.split(',')) { // split first char diff --git a/crates/tinymist-query/src/fixtures/references/cross_file_ref_label.typ b/crates/tinymist-query/src/fixtures/references/cross_file_ref_label.typ new file mode 100644 index 00000000..bca2fd1d --- /dev/null +++ b/crates/tinymist-query/src/fixtures/references/cross_file_ref_label.typ @@ -0,0 +1,19 @@ +// path: base2.typ +== Base 2 +Ref to b1 @b1 +Ref to b2 @b2 +Ref to b1 @b1 again +----- +// path: base1.typ +== Base 1 +Ref to b1 @b1 +Ref to b2 @b2 +----- +// compile:true + +#set heading(numbering: "1.") += Test Ref Label +#include "base1.typ" +#include "base2.typ" +Ref to b1 /* position after */ @b1 +Ref to b2 @b2 \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/references/label.typ b/crates/tinymist-query/src/fixtures/references/label.typ new file mode 100644 index 00000000..36fb3a50 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/references/label.typ @@ -0,0 +1,6 @@ +// compile: true +#set heading(numbering: "1.") + += Labeled /* ident */ + +@title_label diff --git a/crates/tinymist-query/src/fixtures/references/ref_label.typ b/crates/tinymist-query/src/fixtures/references/ref_label.typ new file mode 100644 index 00000000..f95dbc4e --- /dev/null +++ b/crates/tinymist-query/src/fixtures/references/ref_label.typ @@ -0,0 +1,10 @@ +// compile: true +#set heading(numbering: "1.") + += Labeled + +@title_label + +/* position after */ @title_label + +@title_label \ No newline at end of file diff --git a/crates/tinymist-query/src/fixtures/references/snaps/test@cross_file_ref_label.typ.snap b/crates/tinymist-query/src/fixtures/references/snaps/test@cross_file_ref_label.typ.snap new file mode 100644 index 00000000..ef1ac67e --- /dev/null +++ b/crates/tinymist-query/src/fixtures/references/snaps/test@cross_file_ref_label.typ.snap @@ -0,0 +1,11 @@ +--- +source: crates/tinymist-query/src/references.rs +expression: "JsonRepr::new_pure(result)" +input_file: crates/tinymist-query/src/fixtures/references/cross_file_ref_label.typ +--- +[ + "/base1.typ@1:10:1:13", + "/base2.typ@1:10:1:13", + "/base2.typ@3:10:3:13", + "/s2.typ@6:31:6:34" +] diff --git a/crates/tinymist-query/src/fixtures/references/snaps/test@label.typ.snap b/crates/tinymist-query/src/fixtures/references/snaps/test@label.typ.snap new file mode 100644 index 00000000..25c5be57 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/references/snaps/test@label.typ.snap @@ -0,0 +1,8 @@ +--- +source: crates/tinymist-query/src/references.rs +expression: "JsonRepr::new_pure(result)" +input_file: crates/tinymist-query/src/fixtures/references/label.typ +--- +[ + "/s0.typ@5:0:5:12" +] diff --git a/crates/tinymist-query/src/fixtures/references/snaps/test@recursive_import.typ.snap b/crates/tinymist-query/src/fixtures/references/snaps/test@recursive_import.typ.snap index f633c09c..c4de5170 100644 --- a/crates/tinymist-query/src/fixtures/references/snaps/test@recursive_import.typ.snap +++ b/crates/tinymist-query/src/fixtures/references/snaps/test@recursive_import.typ.snap @@ -5,7 +5,7 @@ input_file: crates/tinymist-query/src/fixtures/references/recursive_import.typ --- [ "/base2.typ@0:20:0:21", - "/s0.typ@0:21:0:22", "/base2.typ@1:1:1:2", + "/s0.typ@0:21:0:22", "/s0.typ@1:1:1:2" ] diff --git a/crates/tinymist-query/src/fixtures/references/snaps/test@ref_label.typ.snap b/crates/tinymist-query/src/fixtures/references/snaps/test@ref_label.typ.snap new file mode 100644 index 00000000..fe900f1f --- /dev/null +++ b/crates/tinymist-query/src/fixtures/references/snaps/test@ref_label.typ.snap @@ -0,0 +1,10 @@ +--- +source: crates/tinymist-query/src/references.rs +expression: "JsonRepr::new_pure(result)" +input_file: crates/tinymist-query/src/fixtures/references/ref_label.typ +--- +[ + "/s0.typ@5:0:5:12", + "/s0.typ@7:21:7:33", + "/s0.typ@9:0:9:12" +] diff --git a/crates/tinymist-query/src/goto_definition.rs b/crates/tinymist-query/src/goto_definition.rs index fbecc4cb..ccd2d9ac 100644 --- a/crates/tinymist-query/src/goto_definition.rs +++ b/crates/tinymist-query/src/goto_definition.rs @@ -57,8 +57,8 @@ impl StatefulRequest for GotoDefinitionRequest { #[cfg(test)] mod tests { - use crate::syntax::find_module_level_docs; use super::*; + use crate::syntax::find_module_level_docs; use crate::tests::*; #[test] @@ -68,12 +68,7 @@ mod tests { let docs = find_module_level_docs(&source).unwrap_or_default(); let properties = get_test_properties(&docs); - let must_compile = has_test_property(&properties, "compile"); - let doc = if must_compile { - compile_doc_for_test(ctx) - } else { - None - }; + let doc = compile_doc_for_test(ctx, &properties); let request = GotoDefinitionRequest { path: path.clone(), diff --git a/crates/tinymist-query/src/references.rs b/crates/tinymist-query/src/references.rs index a1e4730d..7ae25f6a 100644 --- a/crates/tinymist-query/src/references.rs +++ b/crates/tinymist-query/src/references.rs @@ -1,10 +1,11 @@ +use std::ops::Range; + use log::debug; use crate::{ - analysis::SearchCtx, + analysis::{find_definition, SearchCtx}, prelude::*, syntax::{DerefTarget, IdentRef}, - SemanticRequest, }; /// The [`textDocument/references`] request is sent from the client to the @@ -20,15 +21,18 @@ pub struct ReferencesRequest { pub position: LspPosition, } -impl SemanticRequest for ReferencesRequest { +impl StatefulRequest for ReferencesRequest { type Response = Vec; - fn request(self, ctx: &mut AnalysisContext) -> Option { + fn request( + self, + ctx: &mut AnalysisContext, + doc: Option, + ) -> Option { let source = ctx.source_by_path(&self.path).ok()?; let deref_target = ctx.deref_syntax_at(&source, self.position, 1)?; - let def_use = ctx.def_use(source.clone())?; - let locations = find_references(ctx, def_use, deref_target)?; + let locations = find_references(ctx, source.clone(), doc.as_ref(), deref_target)?; debug!("references: {locations:?}"); Some(locations) @@ -37,72 +41,45 @@ impl SemanticRequest for ReferencesRequest { pub(crate) fn find_references( ctx: &mut AnalysisContext<'_>, - def_use: Arc, + source: Source, + document: Option<&VersionedDocument>, deref_target: DerefTarget<'_>, ) -> Option> { - let node = match deref_target { - DerefTarget::VarAccess(node) => node, - DerefTarget::Callee(node) => node, - DerefTarget::ImportPath(..) | DerefTarget::IncludePath(..) => { - return None; - } - // todo: label, reference - DerefTarget::Label(..) | DerefTarget::Ref(..) | DerefTarget::Normal(..) => { + let finding_label = match deref_target { + DerefTarget::VarAccess(..) | DerefTarget::Callee(..) => false, + DerefTarget::Label(..) | DerefTarget::Ref(..) => true, + DerefTarget::ImportPath(..) | DerefTarget::IncludePath(..) | DerefTarget::Normal(..) => { return None; } }; - let mut may_ident = node.cast::()?; - let name; - loop { - match may_ident { - ast::Expr::Parenthesized(e) => { - may_ident = e.expr(); - } - ast::Expr::FieldAccess(e) => { - may_ident = e.target(); - } - ast::Expr::MathIdent(e) => { - name = e.get().to_string(); - break; - } - ast::Expr::Ident(e) => { - name = e.get().to_string(); - break; - } - _ => return None, - } - } + let def = find_definition(ctx, source, document, deref_target)?; - let ident = node.find(may_ident.span())?; + // todo: reference of builtin items? + let (def_fid, def_range) = def.def_at?; - // todo: if it is exported, find all the references in the workspace - let ident_ref = IdentRef { - name: name.clone(), - range: ident.range(), - }; - let cur_fid = ident.span().id()?; - - let def_id = def_use.get_ref(&ident_ref); - let def_id = def_id.or_else(|| Some(def_use.get_def(cur_fid, &ident_ref)?.0)); - let (def_fid, def) = def_id.and_then(|def_id| def_use.get_def_by_id(def_id))?; let def_ident = IdentRef { name: def.name.clone(), - range: def.range.clone(), + range: def_range, }; let def_source = ctx.source_by_id(def_fid).ok()?; let root_def_use = ctx.def_use(def_source)?; - let root_def_id = root_def_use.get_def(def_fid, &def_ident)?.0; + let root_def_id = root_def_use.get_def(def_fid, &def_ident).map(|e| e.0); let worker = ReferencesWorker { ctx: ctx.fork_for_search(), references: vec![], def_fid, def_ident, + finding_label, }; - worker.root(root_def_use, root_def_id) + if finding_label { + worker.label_root() + } else { + worker.ident_root(root_def_use, root_def_id?) + } } struct ReferencesWorker<'a, 'w> { @@ -110,38 +87,28 @@ struct ReferencesWorker<'a, 'w> { references: Vec, def_fid: TypstFileId, def_ident: IdentRef, + finding_label: bool, } impl<'a, 'w> ReferencesWorker<'a, 'w> { - fn file(&mut self, ref_fid: TypstFileId) -> Option<()> { - log::debug!("references: file: {ref_fid:?}"); - let ref_source = self.ctx.ctx.source_by_id(ref_fid).ok()?; - let def_use = self.ctx.ctx.def_use(ref_source.clone())?; + fn label_root(mut self) -> Option> { + let mut ids = vec![]; - let uri = self.ctx.ctx.uri_for_id(ref_fid).ok()?; - - let mut redefines = vec![]; - if let Some((id, _def)) = def_use.get_def(self.def_fid, &self.def_ident) { - self.references.extend(def_use.get_refs(id).map(|r| { - log::debug!("references: at file: {ref_fid:?}, {r:?}"); - let range = self.ctx.ctx.to_lsp_range(r.range.clone(), &ref_source); - - LspLocation { - uri: uri.clone(), - range, - } - })); - redefines.push(id); - - if def_use.is_exported(id) { - self.ctx.push_dependents(ref_fid); + // Collect ids first to avoid deadlocks + self.ctx.ctx.resources.iter_dependencies(&mut |path| { + if let Ok(ref_fid) = self.ctx.ctx.file_id_by_path(&path) { + ids.push(ref_fid); } - }; + }); - Some(()) + for ref_fid in ids { + self.file(ref_fid)?; + } + + Some(self.references) } - fn root( + fn ident_root( mut self, def_use: Arc, def_id: DefId, @@ -149,18 +116,7 @@ impl<'a, 'w> ReferencesWorker<'a, 'w> { let def_source = self.ctx.ctx.source_by_id(self.def_fid).ok()?; let uri = self.ctx.ctx.uri_for_id(self.def_fid).ok()?; - // todo: reuse uri, range to location - self.references = def_use - .get_refs(def_id) - .map(|r| { - let range = self.ctx.ctx.to_lsp_range(r.range.clone(), &def_source); - - LspLocation { - uri: uri.clone(), - range, - } - }) - .collect::>(); + self.push_idents(&def_source, &uri, def_use.get_refs(def_id)); if def_use.is_exported(def_id) { // Find dependents @@ -172,6 +128,49 @@ impl<'a, 'w> ReferencesWorker<'a, 'w> { Some(self.references) } + + fn file(&mut self, ref_fid: TypstFileId) -> Option<()> { + log::debug!("references: file: {ref_fid:?}"); + let ref_source = self.ctx.ctx.source_by_id(ref_fid).ok()?; + let def_use = self.ctx.ctx.def_use(ref_source.clone())?; + let uri = self.ctx.ctx.uri_for_id(ref_fid).ok()?; + + let mut redefines = vec![]; + if let Some((id, _def)) = def_use.get_def(self.def_fid, &self.def_ident) { + self.push_idents(&ref_source, &uri, def_use.get_refs(id)); + + redefines.push(id); + + if def_use.is_exported(id) { + self.ctx.push_dependents(ref_fid); + } + }; + + // All references are not resolved since static analyzers doesn't know anything + // about labels (which is working at runtime). + if self.finding_label { + let label_refs = def_use.label_refs.get(&self.def_ident.name); + self.push_ranges(&ref_source, &uri, label_refs.into_iter().flatten()); + } + + Some(()) + } + + fn push_idents<'b>(&mut self, s: &Source, u: &Url, idents: impl Iterator) { + self.push_ranges(s, u, idents.map(|e| &e.range)); + } + + fn push_ranges<'b>(&mut self, s: &Source, u: &Url, rs: impl Iterator>) { + self.references.extend(rs.map(|rng| { + log::debug!("references: at file: {s:?}, {rng:?}"); + + let range = self.ctx.ctx.to_lsp_range(rng.clone(), s); + LspLocation { + uri: u.clone(), + range, + } + })); + } } #[cfg(test)] @@ -179,30 +178,25 @@ mod tests { use typst_ts_core::path::unix_slash; use super::*; + use crate::syntax::find_module_level_docs; use crate::{tests::*, url_to_path}; #[test] fn test() { - // goto_definition - snapshot_testing("references", &|world, path| { - let source = world.source_by_path(&path).unwrap(); + snapshot_testing("references", &|ctx, path| { + let source = ctx.source_by_path(&path).unwrap(); + + let docs = find_module_level_docs(&source).unwrap_or_default(); + let properties = get_test_properties(&docs); + let doc = compile_doc_for_test(ctx, &properties); let request = ReferencesRequest { path: path.clone(), position: find_test_position(&source), }; - let result = request.request(world); - // sort - let result = result.map(|mut e| { - e.sort_by(|a, b| match a.range.start.cmp(&b.range.start) { - std::cmp::Ordering::Equal => a.range.end.cmp(&b.range.end), - e => e, - }); - e - }); - - let result = result.map(|v| { + let result = request.request(ctx, doc); + let mut result = result.map(|v| { v.into_iter() .map(|l| { let fp = unix_slash(&url_to_path(l.uri)); @@ -217,6 +211,10 @@ mod tests { }) .collect::>() }); + // sort + if let Some(result) = result.as_mut() { + result.sort(); + } assert_snapshot!(JsonRepr::new_pure(result)); }); diff --git a/crates/tinymist-query/src/rename.rs b/crates/tinymist-query/src/rename.rs index f7f294c6..2474aef8 100644 --- a/crates/tinymist-query/src/rename.rs +++ b/crates/tinymist-query/src/rename.rs @@ -30,8 +30,7 @@ impl StatefulRequest for RenameRequest { validate_renaming_definition(&lnk)?; - let def_use = ctx.def_use(source.clone())?; - let references = find_references(ctx, def_use, deref_target)?; + let references = find_references(ctx, source.clone(), doc.as_ref(), deref_target)?; let mut editions = HashMap::new(); diff --git a/crates/tinymist-query/src/tests.rs b/crates/tinymist-query/src/tests.rs index 2a06c1e1..9e8f9d0f 100644 --- a/crates/tinymist-query/src/tests.rs +++ b/crates/tinymist-query/src/tests.rs @@ -1,11 +1,12 @@ use core::fmt; +use std::sync::Arc; use std::{ borrow::Cow, collections::{HashMap, HashSet}, ops::Range, path::{Path, PathBuf}, }; -use std::sync::Arc; + use once_cell::sync::Lazy; pub use serde::Serialize; use serde_json::{ser::PrettyFormatter, Serializer, Value}; @@ -26,7 +27,11 @@ use typst_ts_core::{config::CompileOpts, package::PackageSpec}; pub use insta::assert_snapshot; pub use typst_ts_compiler::TypstSystemWorld; -use crate::{analysis::{Analysis, AnalysisResources}, prelude::AnalysisContext, typst_to_lsp, LspPosition, PositionEncoding, VersionedDocument}; +use crate::{ + analysis::{Analysis, AnalysisResources}, + prelude::AnalysisContext, + typst_to_lsp, LspPosition, PositionEncoding, VersionedDocument, +}; struct WrapWorld<'a>(&'a mut TypstSystemWorld); @@ -91,14 +96,19 @@ pub fn get_test_properties(s: &str) -> HashMap<&'_ str, &'_ str> { props } -pub fn has_test_property(properties: &HashMap<&'_ str, &'_ str>, prop: &str) -> bool { - properties - .get(prop) +pub fn compile_doc_for_test( + ctx: &mut AnalysisContext, + properties: &HashMap<&str, &str>, +) -> Option { + let must_compile = properties + .get("compile") .map(|v| v.trim() == "true") - .unwrap_or(false) -} + .unwrap_or(false); + + if !must_compile { + return None; + } -pub fn compile_doc_for_test(ctx: &mut AnalysisContext) -> Option { let doc = typst::compile(ctx.world(), &mut Default::default()).unwrap(); Some(VersionedDocument { version: 0, @@ -212,9 +222,10 @@ pub fn find_test_position_(s: &Source, offset: usize) -> LspPosition { } use AstMatcher::*; - let re = s.text() - .find("/* position */") - .zip(Some(MatchAny { prev: true })); + let re = s + .text() + .find("/* position */") + .zip(Some(MatchAny { prev: true })); let re = re.or_else(|| { s.text() .find("/* position after */") diff --git a/crates/tinymist/src/server.rs b/crates/tinymist/src/server.rs index dab4361a..62ca4d35 100644 --- a/crates/tinymist/src/server.rs +++ b/crates/tinymist/src/server.rs @@ -1008,7 +1008,7 @@ impl LanguageState { Hover(req) => handle.run_stateful(snap, req, R::Hover), GotoDefinition(req) => handle.run_stateful(snap, req, R::GotoDefinition), GotoDeclaration(req) => handle.run_semantic(snap, req, R::GotoDeclaration), - References(req) => handle.run_semantic(snap, req, R::References), + References(req) => handle.run_stateful(snap, req, R::References), InlayHint(req) => handle.run_semantic(snap, req, R::InlayHint), DocumentHighlight(req) => handle.run_semantic(snap, req, R::DocumentHighlight), DocumentColor(req) => handle.run_semantic(snap, req, R::DocumentColor), diff --git a/tests/e2e/main.rs b/tests/e2e/main.rs index e3fa6dd5..cebcbf31 100644 --- a/tests/e2e/main.rs +++ b/tests/e2e/main.rs @@ -385,7 +385,7 @@ fn e2e() { }); let hash = replay_log(&tinymist_binary, &root.join("vscode")); - insta::assert_snapshot!(hash, @"siphash128_13:94a9c7a7af77d041ff48651e2d17799c"); + insta::assert_snapshot!(hash, @"siphash128_13:16e3c6c677e354093d0ab458a749329d"); } }