microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.2.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

compiler/qsc_doc_gen/src/generate_docs.rs

313lines · 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
105pub fn generate_docs() -> Files {
106 let compilation = Compilation::new();
107 let mut files: Files = vec![];
108
109 let display = &CodeDisplay {
110 compilation: &compilation,
111 };
112
113 let mut toc: FxHashMap<Rc<str>, Vec<String>> = FxHashMap::default();
114 for (_, unit) in &compilation.package_store {
115 let package = &unit.package;
116 for (_, item) in &package.items {
117 if let Some((ns, line)) = generate_doc_for_item(package, item, display, &mut files) {
118 toc.entry(ns).or_default().push(line);
119 }
120 }
121 }
122
123 generate_toc(&mut toc, &mut files);
124
125 files
126}
127
128fn generate_doc_for_item<'a>(
129 package: &'a Package,
130 item: &'a Item,
131 display: &'a CodeDisplay,
132 files: &mut Files,
133) -> Option<(Rc<str>, String)> {
134 // Filter items
135 if item.visibility == Visibility::Internal || matches!(item.kind, ItemKind::Namespace(_, _)) {
136 return None;
137 }
138
139 // Get namespace for item
140 let ns = get_namespace(package, item)?;
141
142 // Add file
143 let (metadata, content) = generate_file(&ns, item, display)?;
144 let file_name: Arc<str> = Arc::from(format!("{ns}/{}.md", metadata.name).as_str());
145 let file_metadata: Arc<str> = Arc::from(metadata.to_string().as_str());
146 let file_content: Arc<str> = Arc::from(content.as_str());
147 files.push((file_name, file_metadata, file_content));
148
149 // Create toc line
150 let line = format!(" - {{name: {}, uid: {}}}", metadata.name, metadata.uid);
151
152 // Return (ns, line)
153 Some((ns.clone(), line))
154}
155
156fn get_namespace(package: &Package, item: &Item) -> Option<Rc<str>> {
157 match item.parent {
158 Some(local_id) => {
159 let parent = package
160 .items
161 .get(local_id)
162 .expect("Could not resolve parent item id");
163 match &parent.kind {
164 ItemKind::Namespace(name, _) => {
165 if name.name.starts_with("QIR") {
166 None // We ignore "QIR" namespaces
167 } else {
168 Some(name.name.clone())
169 }
170 }
171 _ => None,
172 }
173 }
174 None => None,
175 }
176}
177
178fn generate_file(ns: &Rc<str>, item: &Item, display: &CodeDisplay) -> Option<(Metadata, String)> {
179 let metadata = get_metadata(ns.clone(), item, display)?;
180
181 let doc = increase_header_level(&item.doc);
182 let title = &metadata.title;
183 let sig = &metadata.signature;
184
185 let content = format!(
186 "# {title}
187
188Namespace: {ns}
189
190```qsharp
191{sig}
192```
193"
194 );
195
196 let content = if doc.is_empty() {
197 content
198 } else {
199 format!("{content}\n{doc}\n")
200 };
201
202 Some((metadata, content))
203}
204
205struct Metadata {
206 uid: String,
207 title: String,
208 topic: String,
209 kind: MetadataKind,
210 namespace: Rc<str>,
211 name: Rc<str>,
212 summary: String,
213 signature: String,
214}
215
216impl Display for Metadata {
217 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
218 let kind = match &self.kind {
219 MetadataKind::Function => "function",
220 MetadataKind::Operation => "operation",
221 MetadataKind::Udt => "udt",
222 };
223 write!(
224 f,
225 "---
226uid: {}
227title: {}
228ms.date: {{TIMESTAMP}}
229ms.topic: {}
230qsharp.kind: {}
231qsharp.namespace: {}
232qsharp.name: {}
233qsharp.summary: \"{}\"
234---",
235 self.uid, self.title, self.topic, kind, self.namespace, self.name, self.summary
236 )
237 }
238}
239
240enum MetadataKind {
241 Function,
242 Operation,
243 Udt,
244}
245
246fn get_metadata(ns: Rc<str>, item: &Item, display: &CodeDisplay) -> Option<Metadata> {
247 let (name, signature, kind) = match &item.kind {
248 ItemKind::Callable(decl) => Some((
249 decl.name.name.clone(),
250 display.hir_callable_decl(decl).to_string(),
251 match &decl.kind {
252 CallableKind::Function => MetadataKind::Function,
253 CallableKind::Operation => MetadataKind::Operation,
254 },
255 )),
256 ItemKind::Ty(ident, udt) => Some((
257 ident.name.clone(),
258 display.hir_udt(udt).to_string(),
259 MetadataKind::Udt,
260 )),
261 ItemKind::Namespace(_, _) => None,
262 }?;
263
264 let summary = parse_doc_for_summary(&item.doc)
265 .replace("\r\n", " ")
266 .replace('\n', " ");
267
268 Some(Metadata {
269 uid: format!("Qdk.{ns}.{name}"),
270 title: match &kind {
271 MetadataKind::Function => format!("{name} function"),
272 MetadataKind::Operation => format!("{name} operation"),
273 MetadataKind::Udt => format!("{name} user defined type"),
274 },
275 topic: "managed-reference".to_string(),
276 kind,
277 namespace: ns,
278 name,
279 summary,
280 signature,
281 })
282}
283
284/// Generates the Table of Contents file, toc.yml
285fn generate_toc(map: &mut FxHashMap<Rc<str>, Vec<String>>, files: &mut Files) {
286 let header = "
287# This file is automatically generated.
288# Please do not modify this file manually, or your changes will be lost when
289# documentation is rebuilt.";
290 let mut table = map
291 .iter_mut()
292 .map(|(namespace, lines)| {
293 lines.sort_unstable();
294 let items_str = lines.join("\n");
295 let content =
296 format!("- items:\n{items_str}\n name: {namespace}\n uid: Qdk.{namespace}");
297 (namespace, content)
298 })
299 .collect::<Vec<_>>();
300
301 table.sort_unstable_by_key(|(n, _)| *n);
302 let table = table
303 .into_iter()
304 .map(|(_, c)| c)
305 .collect::<Vec<_>>()
306 .join("\n");
307 let content = format!("{header}\n{table}");
308
309 let file_name: Arc<str> = Arc::from("toc.yml");
310 let file_metadata: Arc<str> = Arc::from("");
311 let file_content: Arc<str> = Arc::from(content.as_str());
312 files.push((file_name, file_metadata, file_content));
313}
314