microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.3.1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

compiler/qsc_doc_gen/src/generate_docs.rs

314lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use crate::display::{increase_header_level, parse_doc_for_summary};
8use crate::display::{CodeDisplay, Lookup};
9use qsc_ast::ast;
10use qsc_frontend::compile::{self, PackageStore, RuntimeCapabilityFlags};
11use qsc_frontend::resolve;
12use qsc_hir::hir::{CallableKind, Item, ItemKind, Package, PackageId, Visibility};
13use qsc_hir::{hir, ty};
14use rustc_hash::FxHashMap;
15use std::fmt::{Display, Formatter, Result};
16use std::rc::Rc;
17use std::sync::Arc;
18
19type Files = Vec<(Arc<str>, Arc<str>, Arc<str>)>;
20
21/// Represents an immutable compilation state.
22#[derive(Debug)]
23struct Compilation {
24 /// Package store, containing the current package and all its dependencies.
25 package_store: PackageStore,
26}
27
28impl Compilation {
29 /// Creates a new `Compilation` by compiling sources.
30 pub(crate) fn new() -> Self {
31 let mut package_store = PackageStore::new(compile::core());
32 package_store.insert(compile::std(&package_store, RuntimeCapabilityFlags::all()));
33
34 Self { package_store }
35 }
36}
37
38impl Lookup for Compilation {
39 fn get_ty(&self, _: ast::NodeId) -> Option<&ty::Ty> {
40 unimplemented!("Not needed for docs generation")
41 }
42
43 fn get_res(&self, _: ast::NodeId) -> Option<&resolve::Res> {
44 unimplemented!("Not needed for docs generation")
45 }
46
47 fn resolve_item_relative_to_user_package(
48 &self,
49 _: &hir::ItemId,
50 ) -> (&hir::Item, &hir::Package, hir::ItemId) {
51 unimplemented!("Not needed for docs generation")
52 }
53
54 /// Returns the hir `Item` node referred to by `res`.
55 /// `Res`s can resolve to external packages, and the references
56 /// are relative, so here we also need the
57 /// local `PackageId` that the `res` itself came from.
58 fn resolve_item_res(
59 &self,
60 local_package_id: PackageId,
61 res: &hir::Res,
62 ) -> (&hir::Item, hir::ItemId) {
63 match res {
64 hir::Res::Item(item_id) => {
65 let (item, _, resolved_item_id) = self.resolve_item(local_package_id, item_id);
66 (item, resolved_item_id)
67 }
68 _ => panic!("expected to find item"),
69 }
70 }
71
72 /// Returns the hir `Item` node referred to by `item_id`.
73 /// `ItemId`s can refer to external packages, and the references
74 /// are relative, so here we also need the local `PackageId`
75 /// that the `ItemId` originates from.
76 fn resolve_item(
77 &self,
78 local_package_id: PackageId,
79 item_id: &hir::ItemId,
80 ) -> (&hir::Item, &hir::Package, hir::ItemId) {
81 // If the `ItemId` contains a package id, use that.
82 // Lack of a package id means the item is in the
83 // same package as the one this `ItemId` reference
84 // came from. So use the local package id passed in.
85 let package_id = item_id.package.unwrap_or(local_package_id);
86 let package = &self
87 .package_store
88 .get(package_id)
89 .expect("package should exist in store")
90 .package;
91 (
92 package
93 .items
94 .get(item_id.item)
95 .expect("item id should exist"),
96 package,
97 hir::ItemId {
98 package: Some(package_id),
99 item: item_id.item,
100 },
101 )
102 }
103}
104
105#[must_use]
106pub fn generate_docs() -> Files {
107 let compilation = Compilation::new();
108 let mut files: Files = vec![];
109
110 let display = &CodeDisplay {
111 compilation: &compilation,
112 };
113
114 let mut toc: FxHashMap<Rc<str>, Vec<String>> = FxHashMap::default();
115 for (_, unit) in &compilation.package_store {
116 let package = &unit.package;
117 for (_, item) in &package.items {
118 if let Some((ns, line)) = generate_doc_for_item(package, item, display, &mut files) {
119 toc.entry(ns).or_default().push(line);
120 }
121 }
122 }
123
124 generate_toc(&mut toc, &mut files);
125
126 files
127}
128
129fn generate_doc_for_item<'a>(
130 package: &'a Package,
131 item: &'a Item,
132 display: &'a CodeDisplay,
133 files: &mut Files,
134) -> Option<(Rc<str>, String)> {
135 // Filter items
136 if item.visibility == Visibility::Internal || matches!(item.kind, ItemKind::Namespace(_, _)) {
137 return None;
138 }
139
140 // Get namespace for item
141 let ns = get_namespace(package, item)?;
142
143 // Add file
144 let (metadata, content) = generate_file(&ns, item, display)?;
145 let file_name: Arc<str> = Arc::from(format!("{ns}/{}.md", metadata.name).as_str());
146 let file_metadata: Arc<str> = Arc::from(metadata.to_string().as_str());
147 let file_content: Arc<str> = Arc::from(content.as_str());
148 files.push((file_name, file_metadata, file_content));
149
150 // Create toc line
151 let line = format!(" - {{name: {}, uid: {}}}", metadata.name, metadata.uid);
152
153 // Return (ns, line)
154 Some((ns.clone(), line))
155}
156
157fn get_namespace(package: &Package, item: &Item) -> Option<Rc<str>> {
158 match item.parent {
159 Some(local_id) => {
160 let parent = package
161 .items
162 .get(local_id)
163 .expect("Could not resolve parent item id");
164 match &parent.kind {
165 ItemKind::Namespace(name, _) => {
166 if name.name.starts_with("QIR") {
167 None // We ignore "QIR" namespaces
168 } else {
169 Some(name.name.clone())
170 }
171 }
172 _ => None,
173 }
174 }
175 None => None,
176 }
177}
178
179fn generate_file(ns: &Rc<str>, item: &Item, display: &CodeDisplay) -> Option<(Metadata, String)> {
180 let metadata = get_metadata(ns.clone(), item, display)?;
181
182 let doc = increase_header_level(&item.doc);
183 let title = &metadata.title;
184 let sig = &metadata.signature;
185
186 let content = format!(
187 "# {title}
188
189Namespace: {ns}
190
191```qsharp
192{sig}
193```
194"
195 );
196
197 let content = if doc.is_empty() {
198 content
199 } else {
200 format!("{content}\n{doc}\n")
201 };
202
203 Some((metadata, content))
204}
205
206struct Metadata {
207 uid: String,
208 title: String,
209 topic: String,
210 kind: MetadataKind,
211 namespace: Rc<str>,
212 name: Rc<str>,
213 summary: String,
214 signature: String,
215}
216
217impl Display for Metadata {
218 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
219 let kind = match &self.kind {
220 MetadataKind::Function => "function",
221 MetadataKind::Operation => "operation",
222 MetadataKind::Udt => "udt",
223 };
224 write!(
225 f,
226 "---
227uid: {}
228title: {}
229ms.date: {{TIMESTAMP}}
230ms.topic: {}
231qsharp.kind: {}
232qsharp.namespace: {}
233qsharp.name: {}
234qsharp.summary: \"{}\"
235---",
236 self.uid, self.title, self.topic, kind, self.namespace, self.name, self.summary
237 )
238 }
239}
240
241enum MetadataKind {
242 Function,
243 Operation,
244 Udt,
245}
246
247fn get_metadata(ns: Rc<str>, item: &Item, display: &CodeDisplay) -> Option<Metadata> {
248 let (name, signature, kind) = match &item.kind {
249 ItemKind::Callable(decl) => Some((
250 decl.name.name.clone(),
251 display.hir_callable_decl(decl).to_string(),
252 match &decl.kind {
253 CallableKind::Function => MetadataKind::Function,
254 CallableKind::Operation => MetadataKind::Operation,
255 },
256 )),
257 ItemKind::Ty(ident, udt) => Some((
258 ident.name.clone(),
259 display.hir_udt(udt).to_string(),
260 MetadataKind::Udt,
261 )),
262 ItemKind::Namespace(_, _) => None,
263 }?;
264
265 let summary = parse_doc_for_summary(&item.doc)
266 .replace("\r\n", " ")
267 .replace('\n', " ");
268
269 Some(Metadata {
270 uid: format!("Qdk.{ns}.{name}"),
271 title: match &kind {
272 MetadataKind::Function => format!("{name} function"),
273 MetadataKind::Operation => format!("{name} operation"),
274 MetadataKind::Udt => format!("{name} user defined type"),
275 },
276 topic: "managed-reference".to_string(),
277 kind,
278 namespace: ns,
279 name,
280 summary,
281 signature,
282 })
283}
284
285/// Generates the Table of Contents file, toc.yml
286fn generate_toc(map: &mut FxHashMap<Rc<str>, Vec<String>>, files: &mut Files) {
287 let header = "
288# This file is automatically generated.
289# Please do not modify this file manually, or your changes will be lost when
290# documentation is rebuilt.";
291 let mut table = map
292 .iter_mut()
293 .map(|(namespace, lines)| {
294 lines.sort_unstable();
295 let items_str = lines.join("\n");
296 let content =
297 format!("- items:\n{items_str}\n name: {namespace}\n uid: Qdk.{namespace}");
298 (namespace, content)
299 })
300 .collect::<Vec<_>>();
301
302 table.sort_unstable_by_key(|(n, _)| *n);
303 let table = table
304 .into_iter()
305 .map(|(_, c)| c)
306 .collect::<Vec<_>>()
307 .join("\n");
308 let content = format!("{header}\n{table}");
309
310 let file_name: Arc<str> = Arc::from("toc.yml");
311 let file_metadata: Arc<str> = Arc::from("");
312 let file_content: Arc<str> = Arc::from(content.as_str());
313 files.push((file_name, file_metadata, file_content));
314}
315