Skip to main content

ra_ap_rust_analyzer/cli/
lsif.rs

1//! LSIF (language server index format) generator
2use std::env;
3use std::time::Instant;
4
5use ide::{
6    Analysis, AnalysisHost, FileId, FileRange, MonikerKind, MonikerResult, PackageInformation,
7    RootDatabase, StaticIndex, StaticIndexedFile, TokenId, TokenStaticData,
8    VendoredLibrariesConfig,
9};
10use ide_db::{line_index, line_index::WideEncoding};
11use load_cargo::{LoadCargoConfig, ProcMacroServerChoice, load_workspace};
12use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace, RustLibSource};
13use rustc_hash::FxHashMap;
14use stdx::format_to;
15use vendored as lsif;
16use vfs::{AbsPathBuf, Vfs};
17
18mod vendored;
19
20use crate::{
21    cli::flags,
22    line_index::{LineEndings, LineIndex, PositionEncoding},
23    lsp::to_proto,
24    version::version,
25};
26
27struct LsifManager<'a, 'w> {
28    count: i32,
29    token_map: FxHashMap<TokenId, Id>,
30    range_map: FxHashMap<FileRange, Id>,
31    file_map: FxHashMap<FileId, Id>,
32    package_map: FxHashMap<PackageInformation, Id>,
33    analysis: &'a Analysis,
34    db: &'a RootDatabase,
35    vfs: &'a Vfs,
36    out: &'w mut dyn std::io::Write,
37}
38
39#[derive(Clone, Copy)]
40struct Id(i32);
41
42impl From<Id> for lsp_types::Code {
43    fn from(Id(it): Id) -> Self {
44        lsp_types::Code::Int(it)
45    }
46}
47
48impl LsifManager<'_, '_> {
49    fn new<'a, 'w>(
50        analysis: &'a Analysis,
51        db: &'a RootDatabase,
52        vfs: &'a Vfs,
53        out: &'w mut dyn std::io::Write,
54    ) -> LsifManager<'a, 'w> {
55        LsifManager {
56            count: 0,
57            token_map: FxHashMap::default(),
58            range_map: FxHashMap::default(),
59            file_map: FxHashMap::default(),
60            package_map: FxHashMap::default(),
61            analysis,
62            db,
63            vfs,
64            out,
65        }
66    }
67
68    fn add(&mut self, data: lsif::Element) -> Id {
69        let id = Id(self.count);
70        self.emit(&serde_json::to_string(&lsif::Entry { id: id.into(), data }).unwrap());
71        self.count += 1;
72        id
73    }
74
75    fn add_vertex(&mut self, vertex: lsif::Vertex) -> Id {
76        self.add(lsif::Element::Vertex(vertex))
77    }
78
79    fn add_edge(&mut self, edge: lsif::Edge) -> Id {
80        self.add(lsif::Element::Edge(edge))
81    }
82
83    fn emit(&mut self, data: &str) {
84        format_to!(self.out, "{data}\n");
85    }
86
87    fn get_token_id(&mut self, id: TokenId) -> Id {
88        if let Some(it) = self.token_map.get(&id) {
89            return *it;
90        }
91        let result_set_id = self.add_vertex(lsif::Vertex::ResultSet(lsif::ResultSet { key: None }));
92        self.token_map.insert(id, result_set_id);
93        result_set_id
94    }
95
96    fn get_package_id(&mut self, package_information: PackageInformation) -> Id {
97        if let Some(it) = self.package_map.get(&package_information) {
98            return *it;
99        }
100        let pi = package_information.clone();
101        let result_set_id =
102            self.add_vertex(lsif::Vertex::PackageInformation(lsif::PackageInformation {
103                name: pi.name,
104                manager: "cargo".to_owned(),
105                uri: None,
106                content: None,
107                repository: pi.repo.map(|url| lsif::Repository {
108                    url,
109                    r#type: "git".to_owned(),
110                    commit_id: None,
111                }),
112                version: pi.version,
113            }));
114        self.package_map.insert(package_information, result_set_id);
115        result_set_id
116    }
117
118    fn get_range_id(&mut self, id: FileRange) -> Id {
119        if let Some(it) = self.range_map.get(&id) {
120            return *it;
121        }
122        let file_id = id.file_id;
123        let doc_id = self.get_file_id(file_id);
124        let line_index = line_index(self.db, file_id);
125        let line_index = LineIndex {
126            index: line_index.clone(),
127            encoding: PositionEncoding::Wide(WideEncoding::Utf16),
128            endings: LineEndings::Unix,
129        };
130        let range_id = self.add_vertex(lsif::Vertex::Range {
131            range: to_proto::range(&line_index, id.range),
132            tag: None,
133        });
134        self.add_edge(lsif::Edge::Contains(lsif::EdgeDataMultiIn {
135            in_vs: vec![range_id.into()],
136            out_v: doc_id.into(),
137        }));
138        range_id
139    }
140
141    fn get_file_id(&mut self, id: FileId) -> Id {
142        if let Some(it) = self.file_map.get(&id) {
143            return *it;
144        }
145        let path = self.vfs.file_path(id);
146        let path = path.as_path().unwrap();
147        let doc_id = self.add_vertex(lsif::Vertex::Document(lsif::Document {
148            language_id: "rust".to_owned(),
149            uri: lsp_types::Uri::from_file_path(path).unwrap(),
150        }));
151        self.file_map.insert(id, doc_id);
152        doc_id
153    }
154
155    fn add_token(&mut self, id: TokenId, token: TokenStaticData) {
156        let result_set_id = self.get_token_id(id);
157        if let Some(hover) = token.hover {
158            let hover_id = self.add_vertex(lsif::Vertex::HoverResult {
159                result: lsp_types::Hover {
160                    contents: lsp_types::Contents::MarkupContent(to_proto::markup_content(
161                        hover.markup,
162                        ide::HoverDocFormat::Markdown,
163                    )),
164                    range: None,
165                },
166            });
167            self.add_edge(lsif::Edge::Hover(lsif::EdgeData {
168                in_v: hover_id.into(),
169                out_v: result_set_id.into(),
170            }));
171        }
172        if let Some(MonikerResult::Moniker(moniker)) = token.moniker {
173            let package_id = self.get_package_id(moniker.package_information);
174            let moniker_id = self.add_vertex(lsif::Vertex::Moniker(lsp_types::Moniker {
175                scheme: "rust-analyzer".to_owned(),
176                identifier: moniker.identifier.to_string(),
177                unique: lsp_types::UniquenessLevel::Scheme,
178                kind: Some(match moniker.kind {
179                    MonikerKind::Import => lsp_types::MonikerKind::Import,
180                    MonikerKind::Export => lsp_types::MonikerKind::Export,
181                }),
182            }));
183            self.add_edge(lsif::Edge::PackageInformation(lsif::EdgeData {
184                in_v: package_id.into(),
185                out_v: moniker_id.into(),
186            }));
187            self.add_edge(lsif::Edge::Moniker(lsif::EdgeData {
188                in_v: moniker_id.into(),
189                out_v: result_set_id.into(),
190            }));
191        }
192        if let Some(def) = token.definition {
193            let result_id = self.add_vertex(lsif::Vertex::DefinitionResult);
194            let def_vertex = self.get_range_id(def);
195            self.add_edge(lsif::Edge::Item(lsif::Item {
196                document: (*self.file_map.get(&def.file_id).unwrap()).into(),
197                property: None,
198                edge_data: lsif::EdgeDataMultiIn {
199                    in_vs: vec![def_vertex.into()],
200                    out_v: result_id.into(),
201                },
202            }));
203            self.add_edge(lsif::Edge::Definition(lsif::EdgeData {
204                in_v: result_id.into(),
205                out_v: result_set_id.into(),
206            }));
207        }
208        if !token.references.is_empty() {
209            let result_id = self.add_vertex(lsif::Vertex::ReferenceResult);
210            self.add_edge(lsif::Edge::References(lsif::EdgeData {
211                in_v: result_id.into(),
212                out_v: result_set_id.into(),
213            }));
214            let mut edges = token.references.iter().fold(
215                FxHashMap::<_, Vec<lsp_types::Code>>::default(),
216                |mut edges, it| {
217                    let entry = edges.entry((it.range.file_id, it.is_definition)).or_default();
218                    entry.push((*self.range_map.get(&it.range).unwrap()).into());
219                    edges
220                },
221            );
222            for it in token.references {
223                if let Some(vertices) = edges.remove(&(it.range.file_id, it.is_definition)) {
224                    self.add_edge(lsif::Edge::Item(lsif::Item {
225                        document: (*self.file_map.get(&it.range.file_id).unwrap()).into(),
226                        property: Some(if it.is_definition {
227                            lsif::ItemKind::Definitions
228                        } else {
229                            lsif::ItemKind::References
230                        }),
231                        edge_data: lsif::EdgeDataMultiIn {
232                            in_vs: vertices,
233                            out_v: result_id.into(),
234                        },
235                    }));
236                }
237            }
238        }
239    }
240
241    fn add_file(&mut self, file: StaticIndexedFile) {
242        let StaticIndexedFile { file_id, tokens, folds, .. } = file;
243        let doc_id = self.get_file_id(file_id);
244        let text = self.analysis.file_text(file_id).unwrap();
245        let line_index = line_index(self.db, file_id);
246        let line_index = LineIndex {
247            index: line_index.clone(),
248            encoding: PositionEncoding::Wide(WideEncoding::Utf16),
249            endings: LineEndings::Unix,
250        };
251        let result = folds
252            .into_iter()
253            .map(|it| to_proto::folding_range(&text, &line_index, false, it))
254            .collect();
255        let folding_id = self.add_vertex(lsif::Vertex::FoldingRangeResult { result });
256        self.add_edge(lsif::Edge::FoldingRange(lsif::EdgeData {
257            in_v: folding_id.into(),
258            out_v: doc_id.into(),
259        }));
260        let tokens_id = tokens
261            .into_iter()
262            .map(|(range, id)| {
263                let range_id = self.add_vertex(lsif::Vertex::Range {
264                    range: to_proto::range(&line_index, range),
265                    tag: None,
266                });
267                self.range_map.insert(FileRange { file_id, range }, range_id);
268                let result_set_id = self.get_token_id(id);
269                self.add_edge(lsif::Edge::Next(lsif::EdgeData {
270                    in_v: result_set_id.into(),
271                    out_v: range_id.into(),
272                }));
273                range_id.into()
274            })
275            .collect();
276        self.add_edge(lsif::Edge::Contains(lsif::EdgeDataMultiIn {
277            in_vs: tokens_id,
278            out_v: doc_id.into(),
279        }));
280    }
281}
282
283impl flags::Lsif {
284    pub fn run(
285        self,
286        out: &mut dyn std::io::Write,
287        sysroot: Option<RustLibSource>,
288    ) -> anyhow::Result<()> {
289        let now = Instant::now();
290        let cargo_config =
291            &CargoConfig { sysroot, all_targets: true, set_test: true, ..Default::default() };
292        let no_progress = &|_| ();
293        let load_cargo_config = LoadCargoConfig {
294            load_out_dirs_from_check: true,
295            with_proc_macro_server: ProcMacroServerChoice::Sysroot,
296            prefill_caches: false,
297            num_worker_threads: 1,
298            proc_macro_processes: 1,
299        };
300        let path = AbsPathBuf::assert_utf8(env::current_dir()?.join(self.path));
301        let root = ProjectManifest::discover_single(&path)?;
302        eprintln!("Generating LSIF for project at {root}");
303        let mut workspace = ProjectWorkspace::load(root, cargo_config, no_progress)?;
304
305        let build_scripts = workspace.run_build_scripts(cargo_config, no_progress)?;
306        workspace.set_build_scripts(build_scripts);
307
308        let (db, vfs, _proc_macro) =
309            load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config)?;
310        let host = AnalysisHost::with_database(db);
311        let db = host.raw_database();
312        let analysis = host.analysis();
313
314        let vendored_libs_config = if self.exclude_vendored_libraries {
315            VendoredLibrariesConfig::Excluded
316        } else {
317            VendoredLibrariesConfig::Included { workspace_root: &path.clone().into() }
318        };
319
320        let si = StaticIndex::compute(&analysis, vendored_libs_config);
321
322        let mut lsif = LsifManager::new(&analysis, db, &vfs, out);
323        lsif.add_vertex(lsif::Vertex::MetaData(lsif::MetaData {
324            version: String::from("0.5.0"),
325            project_root: lsp_types::Uri::from_file_path(path).unwrap(),
326            position_encoding: lsif::Encoding::Utf16,
327            tool_info: Some(lsif::ToolInfo {
328                name: "rust-analyzer".to_owned(),
329                args: vec![],
330                version: Some(version().to_string()),
331            }),
332        }));
333        for file in si.files {
334            lsif.add_file(file);
335        }
336        for (id, token) in si.tokens.iter() {
337            lsif.add_token(id, token);
338        }
339        eprintln!("Generating LSIF finished in {:?}", now.elapsed());
340        Ok(())
341    }
342}